If statement in for loop referencing incremented variable - google-apps-script

as quite an novice when it comes to coding, I ran into this problem with my code:
I use Google App Script [Edit: Corrected Google App Engine to Google App Script] to go through a list of timestamps and filter for stamps that equal the current month. Therefor I load the spreadsheet, the according sheet and get the data from all the rows as an object.
In the next step I go though all the elements of the object and check whether they contain the current date.
/* Initial data */
var email = "name#domain.com";
var spreadsheet = SpreadsheetApp.openById("1bN7PTOa6PwryVvcGxzDxuNVkeZMRwYKAGFnQvxJ_0nU");
var tasklist = spreadsheet.getSheets()[0].getDataRange();
var tasks = tasklist.getValues();
var tasksnum = tasklist.getNumRows();
Logger.log(tasks[7][2]); //Console returns "01.12.2014"
Logger.log(tasks[7][2].indexOf(month)); //Console returns "12.2014"
/* Filter tasks by month */
for (var i = 1; i < 9; i++) {
if (tasks[i][2].indexOf(month) >= 0) {
Logger.log(tasks[i]);
}
else {
return;
}
}
What drives me crazy is the following: As stated above, the for loop doesn't work. But if I alter it like this
if (tasks[7][2].indexOf(month) >= o) {
it works like a charm. And that's what I don't get. i should be incremented til 9 so should be seven at some point. At least then, the condition should be true and the loop should return a log.
What am I missing?
Thank you all in advance.
ps: If I am just following the wrong path of how to implement the function, please let me know.
ps2: I think my question's title is a bit cryptic. If you have a better one in mind, I'd love to change it.

The reason your original code doesn't work is due to the return statement in your else block. Return exits the function immediately, so your loop never gets past the first "false" of your IF.
remove the "return" (or the else block entirely) and your code should work.

I'm not sure what's supposed to be in the month variable but I suggested building a string to match your date format and doing string comparisons instead of the indexOf() strategy you're trying. This code is tested and does just that:
function test() {
/* Initial data */
var spreadsheet = SpreadsheetApp.openById("");
var tasklist = spreadsheet.getSheets()[0].getDataRange();
var tasks = tasklist.getValues();
var tasksnum = tasklist.getNumRows();
/* Filter tasks by month */
var today = Utilities.formatDate(
new Date(),
spreadsheet.getSpreadsheetTimeZone(),
"dd.MM.yyyy"
);
for(var i=1; i<9; i++){
if (tasks[i][2] == today){
Logger.log(tasks[i]);
}
}
}

Related

Remove duplicates based on one column and keep latest entry in google sheets (while ignoring blank entries in that column)

I'm working on a spreadsheet where data is added in real time by an API. The data from the API is from users who sign up for a newsletter consists of basic data. When data is send from de API, it is added as a new row in the spreadsheet.
Users also have the option to answer additional newsletter questions later, this will also cause the API to add a new row, with additional data that is placed in different columns, but also still show the existing data that was previously known.
To avoid clutter, I want to remove duplicates based on one column and keep the last entry in Google Sheets. Which results in removing the old basic data row and only keeping the row with additional data. To highlight that this is data that is "updated" by the user, I also highlight this row. The data used to mark submissions as duplicates will be based on a user's email address. Since this will remain the same in both cases. [I may have to be careful with uppercase and lowercase letters, where the script doesn't see two emails as duplicates, I don't have an answer for that yet]
Besides this I already have a script in place that adds current time and date to an added row and places it in the first colomn.
For the duplicate issue, I already found a simular question Remove duplicates based on one column and keep latest entry in google sheets and the solution from Tanaike was very helpfull. Overall this code works for me, but sometimes it seems that the script runs when it's not suppose to.
My current script looks like this:
function onChange(e){
if (e.changeType == 'INSERT_ROW'){
const sh = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
sh.getRange(sh.getActiveRange().getRow(), 1).setValue(new Date());
}
}
function removeDuplicates() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getSheetByName('Inzendingen');
var dt = sh.getDataRange().getValues();
var uA = [];
for (var i = dt.length - 1; i >= 0; i--) {
if (uA.indexOf(dt[i][4]) == -1) {
uA.push(dt[i][4]);
} else {
sh.deleteRow(i + 1);
Utilities.sleep(500);
sh.getRange(sh.getLastRow(),1,1,sh.getLastColumn()).setBackground('lightblue');
}
}
}
I added Utilities.sleep(500); to prevent the scenario where the row that's being deleted was faster than the highlight. So to prevent having an empty highlighted row at the bottom underneath the latest entered row.
Both scripts are setup with the trigger: From Spreadsheet - On Change
If everything works as planned, it should work something like this (all fake data, no worries):
My problem is as follows:
Currently some new users that are being added by the API for the first time, are also being highlighted. I suspect this has something to do with the fact that the duplicate deletion also works when the value of the email colomn is empty. However, this is only an assumption given the limited knowledge I have of these matters.
Seen is this example:
Long story short
I would love for this script to work as I intend it to do, where it only removes duplicates based on a duplicate email adress in colomn E. It would be even better if the duplicate deletion script also ignores capitalization. And lastly that it also ignores blank entries in colomn E.
I tried to use the script Remove duplicates based on one column and keep latest entry in google sheets
And make some addition in this script. Stuff like:
function removeDuplicates() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getSheetByName('Inzendingen');
var dt = sh.getDataRange().getValues();
var uA = [];
for (var i = dt.length - 1; i >= 0; i--) {
if (uA.indexOf(dt[i][4]) == -1 && dt.length !=0 ) {
uA.push(dt[i][4]);
} else {
sh.deleteRow(i + 1);
sh.getRange(sh.getLastRow(),1,1,sh.getLastColumn()).setBackground('lightblue');
}
}
}
Where I thought adding && dt.length !=0 would signal to the "if" to only trigger when there's a duplicate and when the value/length is not 0.
If I understand you correctly, the only issue is around these new people with no emails being highlighted. I believe you are on the right track, but you have dt.length != 0, which is looking at the entire array. Instead, you want to just check for the email.
As such, you can use this:
dt[i][4].length != 0
or
dt[i][4] != ""
EDIT:
I believe this will give the results you want. Blank emails are ignored, and duplicate emails ignore case.
function removeDuplicates() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getSheetByName('Inzendingen');
var dtAll = sh.getDataRange().getValues();
dt = dtAll.map(function(f){return [f[4].toLowerCase()]});
var uA = [];
for (var i = dt.length - 1; i >= 0; i--) {
if (uA.indexOf(dt[i][0]) == -1) {
uA.push(dt[i][0]);
Logger.log(uA[i]);
} else if (dt[i][0] != ""){
sh.deleteRow(i + 1);
sh.getRange(sh.getLastRow(),1,1,sh.getLastColumn()).setBackground('lightblue');
}
}
}

Google Sheets API / JS: find a value in sheet (anywhere)

I'm exporting data from Google Analytics to put it in Google Sheets. I have created a sheet called "raw" where I will dump all the data. In order to avoid exporting and dumping the data, I need to check if the data is already there. I don't need to know where it is, I just need to know where it is.
I have written a function to get the data out of Google Analytics and it works as when I display the output in the logs, I get what I need.
result = getData(tableId,dimensions,filters,'2021-01-01','2021-01-01',metrics);
Logger.log(result['rows']);
Now I want to loop through those results and check if the data is already available in the sheet. If it's not yet available, I would add it to the bottom of the sheet
var raw = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("raw");
var raw_content = raw.getDataRange();
From here, I used the script available here: How to check if the value exist in google spreadsheet or not using apps script
for (var b=0;b<result['rows'].length;b++)
{
temp_date = result['rows'][b][0];
temp_value = result['rows'][b][1];
unique_id = label+'_'+temp_date;
Logger.log(unique_id);
var textFinder = raw_content.createTextFinder(unique_id);
var occurrences = textFinder.findAll().map(x => x.getA1Notation());
if (occurrences == [])
{
Logger.log('not found');
}
else
{
Logger.log('found');
}
}
Even if the sheet is empty, it always return "found". If I add manually one of the unique ids, it still returns "found" all the time.
I have also tried the second alternative in this post but it doesn't work.
I'd like to avoid using a loop to check cells one by one because at some point, there will be a lot of data in there.
Any idea?
thanks
From your code and what you are trying to accomplish, you would need to define textFinder for each iteration of the loop, with different values of label+'_'+temp_date.
Update: Two arrays in JavaScript cannot be compared with == as it would always return false. The best way in this case is to check its length if zero or not.
for (var b=0;b<result['rows'].length;b++)
{
temp_date = result['rows'][b][0];
temp_value = result['rows'][b][1];
unique_id = label+'_'+temp_date;
var textFinder = raw_content.createTextFinder(unique_id);
var occurrences = textFinder.findAll().map(x => x.getA1Notation());
if (occurrences.length)
{
Logger.log('found');
}
else
{
Logger.log('not found');
}
}
Sample Result:
Reference:
Class TextFinder

How to print variables in google script

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.

Google App script - setValues() doesn't work

So, I'm trying to write a script using the onEdit() event, which will basically remove links that are duplicates (technically, it removes everything, and only puts back things which aren't duplicates).
My code works fine all the way until it's time to write back non-duplicates. Namely, the line in which I use range.setValues(). I understand that it needs an array of arrays of cells which to edit, and that said array needs to fit in the range.
So far, I have :
if (unique)
{
newData.push(editedRow[0]);
Browser.msgBox(newData);
}
Unique is a variable I use that is false if an exact entry was found. With the msgBox command, I can verify that newData contains what it needs to contain. Further down, I have :
newDataFinal = [newData];
Browser.msgBox('Put values '+newDataFinal+' in range ' +range.getA1Notation());
range.setValues(newDataFinal);
To my knowledge, this should make NewDataFinal an array of arrays, which I can verify if I change setValues() to setValue(), which writes [[22.0, 13.0, 23.0]] (for my example) in the spreadsheet, which looks like an array of arrays to me.
The range should also match, since for this example, I get a prompt along the lines of "Put values 22,13,23 in range B2:B4" from the msgBox, which seems as a fitting range.
So, what am I doing wrong?
Here's the rest of the code (please excuse the abundancy of comments/msgboxes and lack of elegancy, the priority is to get it to work, I can probably optimize it and clean it up a bunch afterwards) :
function onEdit(e)
{
var range = e.range;
var values = range.getValues();
var sheet = SpreadsheetApp.getActiveSheet();
if (sheet.getName() != 'testiranje') return;
newData = new Array();
// Browser.msgBox(range.getA1Notation());
range.clear();
var data = sheet.getDataRange().getValues();
var counter = 0;
for (editedRowIndex in values)
{
unique = true;
editedRow = values[editedRowIndex];
// Browser.msgBox('Edited Row ' +editedRow);
for(i in data)
{
var row = data[i];
// Browser.msgBox('Old Row '+row);
for (j in row)
{
// Browser.msgBox(row[j] + ' vs ' + editedRow[0])
if (editedRow[0] == row[j])
{
Browser.msgBox('Hit! '+editedRow[0]);
unique = false;
}
}
}
if (unique)
{
// Browser.msgBox('Pushing '+editedRow[0]+' in newdata');
newData.push(editedRow[0]);
Browser.msgBox(newData);
}
}
newDataFinal = [newData];
Browser.msgBox('Put values '+newDataFinal+' in range ' +range.getA1Notation());
range.setValues(newDataFinal);
// range.setNote('SCIENCE');
}
I didn't test your code because I didn't feel like creating a sheet for it but what I can suggest (that should solve this issue in any case) is to replace your range.setValues(newDataFinal); with this :
sheet.getRange(range.getRowIndex(),range.getColumnIndex(),newDataFinal.length,newDataFinal[0].length).setValues(newDataFinal);
And if you want to know why the range and array didn't fit you can use this code :
(I used Browser because you seem to like it... I prefer Logger.log)
Browser.msgBox('data height = '+newDataFinal.length+', data width = '+newDataFinal[0].length+' and range height is '+range.getHeight()+', range width is '+range.getWidth()+' ... does it fit ?');
Note : I'm almost sure that your initial range is bigger than the newData array since you remove elements from the initial data... My best guess would be that heights don't fit. (but that's only a guess ;-) since you didn't mention the error message you get...)
the problem is that you cant change cells from an onEdit handler. see the docs. instead install your own onEditCustom handler.

Recalling function values in prompt text

I hope you'll forgive me if I have no clue what I'm saying as I've had no real instruction on how to write code. However, I will do my best to explain it well if you will bear with me.
Moving forward, I'm looking to create a prompt that appears upon opening a Google Sheet that will display both a line of text and a line of character values from a function. I may be doing this the most difficult way possible but this is all I have so far:
SpreadsheetApp.getUi().alert('Text goes here' return function lookup(today(),A1:B6));
I'm sure this looks like utter nonsense, but that's why I'm here! I need to be able to recall the contains of the first cell within a row that contains today's date. That is, if a sheet looks something like this:
ABC 3/21 3/28 4/1 4/4 4/7
DEF 3/20 3/29 4/2 4/5 4/9
I need it to read "Text goes here ABC" on the 4th of April and "Text goes here DEF" on the 5th of April.
I hope that makes sense. Please let me know if you need additional information! And I appreciate any help that is offered.
Great question. So first of all, I would recommend you check out Browser.msgBox() (documentation here).
So this isn't super easy, so it may take some work to understand what's going on here. But I created a sheet that works for this. Here's the code:
function onOpen() {
// get the values from the spreadsheet
var ss = SpreadsheetApp.getActiveSheet();
vals = ss.getRange(1, 1, ss.getLastRow(), ss.getLastColumn()).getValues();
// get a greeting based on the date
var greeting = greetingsForDate(vals, formatDate(new Date()));
// display the greeting
Browser.msgBox("Text goes here " + greeting)
}
function greetingsForDate(vals, dateString) {
var greetings = []
// loop through each row
for(var rowNum = 0; rowNum < vals.length; rowNum++) {
rowContent = vals[rowNum];
// loop through each column
for(var columnNum = 1; columnNum < rowContent.length; columnNum++) {
var cellVal = rowContent[columnNum];
// return the value from column A if the current date is in the row
if(typeof cellVal === "object" && formatDate(cellVal) === dateString) {
greetings.push(rowContent[0]);
}
}
}
if(greetings.length === 0) {
return "(none)";
} else {
return greetings.join(", ");
}
}
// returns a formatted date like 04/02/14
function formatDate(date) {
return Utilities.formatDate(date, Session.getScriptTimeZone(), "MM/d/y");
}
It assumes the sheet is structured like this:
ABC 3/21/2014 3/28/2014 4/1/2014 4/4/2014 4/7/2014
DEF 3/20/2014 3/29/2014 4/2/2014 4/5/2014 4/9/2014
If this code doesn't make whole lot of sense, I would highly recommend checking out a site like codecademy.com or codeschool.com. Codecademy's JavaScript course is especially good.
Good luck!
EDITS to return multiple values and check that cells are dates as per the comments.