I've been using a script to pull data from a Google Spreadsheet and use that info to populate a Google Calendar. It works for the most part, but is very unstable: sometimes it does not work, and most times it generates an error, even if it does work.
One error I get a lot is "Cannot find method createAllDayEvent(string,number,object). (line 39, file "")", which, of course, does not make much sense, as that is a known valid method.
Below is my code: I would be super grateful to anyone who can help me tweak it to be a bit more reliable:
Thanks,
Sterling
function SpreadsheetToCalendar()
{
// This function should be executed from the spreadsheet you want to export to the calendar
var mySpreadsheet = SpreadsheetApp.getActiveSheet();
// These 2 methods do the same thing. Using the 2nd one, as 1st is deprecated
// var myCalendar = CalendarApp.openByName("Autodesk Renewals");
var myCalendar = CalendarApp.getCalendarById("rfx.com_bp79fdqvbortgmv21bj8am4hjc#group.calendar.google.com");
// optional - delete existing events
var events = myCalendar.getEvents(new Date("January 1, 2013 PST"),
new Date("December 31, 2016 PST"));
for (var i = 0; i < events.length; i++)
{
events[i].deleteEvent();
Utilities.sleep(300); // pause in the loop for 300 milliseconds
}
var dataRange = mySpreadsheet.getRange("A2:H300");
var data = dataRange.getValues();
// process the data
for (i in data)
{
var row = data[i];
// assume that each row contains a date entry and a text entry
var theContract = row[0] ;
var theDate = row[2] ;
var theCustomer = row[3] ;
var theAssign = row[5] ;
var theStatus = row[6] ;
var theNotes = row[7] ;
//var theTitle = (theCustomer + " | " + theContract + " | " + theAssign + " | " + theStatus);
var theTitle = ("| " + theCustomer + " | " + theContract + " | " + theAssign + " | " + theStatus + " |");
//myCalendar.createAllDayEvent(theTitle, theDate);
// myCalendar.createAllDayEvent(theTitle, new Date(date));
myCalendar.createAllDayEvent(theTitle, theDate, {description:theNotes});
Utilities.sleep(300); // pause in the loop for 300 milliseconds
}
}
as for the specific error regarding createAllDayEvent, note that each parameter has a type. your code is not validating that the cell from row[2] has a valid date and instead its passing a number, which is not expected.
you are also doing a for..in.. on an array which is bad (google it. tldr you will loop on the "count" property too.)
Related
First of all, I'm not an experienced programmer and I'm very new to Google Apps Script.
I'm running a Google Apps Script and I'm stuck. What the script does: it copies a part of a sheet to a temp sheet, makes that into a PDF and sends it by mail.
I want to do this for (right now) 40 mail addresses. If I run the script it gives me a 429 (too many requests) error, after 5 to 8 addresses. This is the heavy part, I found out: var response = UrlFetchApp.fetch(url, params).getBlob(); If I comment it out, it works great, even copy-pasting the temp sheet and sending the emails.
To prevent this I added a sleep timer. I had to go up to 12 seconds and didn't get the error. Great. But now the script takes more than 6 min (the maximum time), so it takes too long and doesn't finish (gets about halfway).
After reading some I think the script (correct me if I'm wrong) is pretty optimal and I need to "chain function calls". But I have no idea how to go about that. I assigned this script to a button in the sheet. But I can't see how I can run 1 function and have that trigger other functions, without it considering that the same function (and thus stopping after 6 min). How do I go about this?
Here's the full code. Sorry for the Dutch text (they are just some confirmation windows and such):
function exportNamedRangesAsPDF() {
var y = 1
var sec = 40
var ui = SpreadsheetApp.getUi();
var result = ui.alert('Weet je zeker dat je alle maandstaten wil versturen via mail?',
ui.ButtonSet.YES_NO);
var html = HtmlService.createHtmlOutputFromFile('Page')
.setWidth(400)
.setHeight(200);
if (result == ui.Button.YES) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Maandstaten print');
var namenSheet = ss.getSheetByName('Alle_namen');
var namenSheetLastRow = namenSheet.getLastRow();
var namenSheetAantalX = namenSheet.getRange("Q1").getValue()
var startKol = 4
var namenVerzonden = [];
var mailOntbreekt = [];
//Logger.log(namenSheetLastRow)
var newSheet = ss.getSheetByName('print');
if (!newSheet) {
newSheet = ss.insertSheet('print');
}
newSheet.showSheet();
var gid = newSheet.getSheetId();
var ssID = "138zfRxR_SQ6oRJouQsMwKQZdyZYbqarUuMCfZTc8fGs";
var url = "https://docs.google.com/spreadsheets/d/"+ssID+"/export"+
"?format=pdf&"+
"size=7&"+
"fzr=false&"+
"portrait=true&"+
"fitw=true&"+
"gridlines=false&"+
"printtitle=false&"+
"sheetnames=false&"+
"pagenum=UNDEFINED&"+
"gid=1186495600&"+
"top_margin=0.75&"+
"bottom_margin=0.75&"+
"left_margin=0.2&"+
"right_margin=0.2&"+
"attachment=true";
var params = {method:"GET",headers:{"authorization":"Bearer "+ ScriptApp.getOAuthToken()}};
var i;
for (i = 1; i < namenSheetLastRow; i++) { //////// START FOR LOOP
if( namenSheet.getRange(i+1,16, 1, 1).getValue() == "x" ) { /////// START IF 1
var startRij = namenSheet.getRange(i+1,15, 1, 1).getValue()
var voornaam = sheet.getRange(startRij+10, startKol-1, 1, 1).getValues();
var zoeknaam = sheet.getRange(startRij+4, startKol-1, 1, 1).getValues();
ui.showModalDialog(html, "Bezig met versturen... " + y + " van " + namenSheetAantalX);
/*if (y % 5 == 0) { // Wait (sleep) every 5th time the script runs, to prevent 429 error (too many reqests)
ui.showModalDialog(html, "Wachten... "+ sec + " sec");
Utilities.sleep(sec*1000); // https://stackoverflow.com/questions/47648338/creating-multiple-google-sheets-pdfs-throws-429-error
Logger.log("Sleep: "+ sec + " sec")
}
y = y+1*/
if( namenSheet.getRange(i+1,16, 1, 1).getValue() == "x" && namenSheet.getRange(i+1,14, 1, 1).getValue() != "" ) { ////////// START IF 2
var volleNaam = sheet.getRange(startRij+2, startKol-2, 1, 1).getValues();
var maand = sheet.getRange(1, 2, 1, 1).getValues();
var mailAdres = sheet.getRange(startRij+9, startKol-1, 1, 1).getValue();
Logger.log(mailAdres + " " + volleNaam);
namenVerzonden.push(" " + zoeknaam);
sheet.getRange(startRij, startKol, 39, 16).copyTo(newSheet.getRange(1, 1, 39, 16), {contentsOnly: true}); //copy the right part of the sheet to the new sheet, content only
sheet.getRange(startRij, startKol, 39, 16).copyTo(newSheet.getRange(1, 1, 39, 16), {formatOnly: true});//copy the right part of the sheet to the new sheet, formatting only
var response = UrlFetchApp.fetch(url, params).getBlob(); // This is the super heavy part, running it too often causes a 429 (too many requests) error
//DriveApp.createFile(response); //save to drive
var message = { //send as email
to: mailAdres,
subject: "Maandstaat "+ maand,
body: "Beste "+ voornaam + ",\n\nIn de bijlage vind je de maandstaat van maand " + maand + ".\n\nMet vriendelijke groet,\nCJ Hendriks Group",
name: "CJ Hendriks",
attachments: [{
fileName: "Maandstaat - " + maand + " - " + volleNaam + ".pdf",
content: response.getBytes(),
mimeType: "application/pdf"
}]
}
//MailApp.sendEmail(message); // This is the actual mail action
} ////////// END IF 2
} /////// END IF 1
else if( namenSheet.getRange(i+1,16, 1, 1).getValue() == "x" && namenSheet.getRange(i+1,14, 1, 1).getValue() == "" ) {
mailOntbreekt.push(" " + zoeknaam);
}
} //////// END FOR LOOP
ui.showModalDialog(html, "Maandstaten verzonden naar: " + namenVerzonden);
Logger.log('Maandstaten verzonden naar: \n'+namenVerzonden);
if( mailOntbreekt.length != 0) {
ui.alert('Mail adres ontbreekt bij: \n'+mailOntbreekt);
}
newSheet.hideSheet(); // hide the "print" sheet
}
else {
ui.alert('Maandstaten NIET verzonden.');
}
Logger.log("Succesvol voltooid")
}
Thank you!
Your script is complex in terms of efficiency
As mentioned in the comment section, you should use arrays instead of getting all the array, this is pretty infeasible for you code. What's more on the line 80 var response = UrlFetchApp.fetch(url, params).getBlob(); you should make this request outside the loop, that's the reason why you are getting 429 because your script's making namenSheetLastRow times a request which is the same response. Keep in mind that Sheets API has its limits when it comes to non-billing accounts, 100 requests per 100 seconds per user.
As a workaround
Move line 80 to line 50 as the following:
...
var params = {method:"GET",headers:{"authorization":"Bearer "+ ScriptApp.getOAuthToken()}};
var response = UrlFetchApp.fetch(url, params).getBlob();
...
In doing so you will have to make this request only once and the bytes will be stored in response.
Reference
Sheets API Limits
So I have this script pulling from a response sheet and running an if statement on each response to check what type of response it is at the current moment I have to types it checks for one being "Register a new Fox" and "Submit a Transaction" I'm pretty new to googlescripts with a decent knowledge of other languages. I'm looking for a solution for why my loop is stopping and how it can be fixed rather, any examples will help a lot. Thanks in advance.
function SetCellData() {
var ss = SpreadsheetApp.getActiveSpreadsheet()
var debugSheet = ss.getSheetByName("Debug")
var famTree = ss.getSheetByName("Family")
var transaction = ss.getSheetByName("Transactions")
var sheetMaster = ss.getSheetByName("Form Responses")
debugSheet.getRange("Debug!A1").setValue(sheetMaster.getRange("Form Responses!C2").getValue());
debugSheet.getRange("Debug!A2").setValue(sheetMaster.getRange("Form Responses!C:C").getLastRow());
var mainRange = sheetMaster.getRange("Form Responses!B2:B");
var registeredFoxes = 0
var transactionsMade = 0
for (i=2; i <= sheetMaster.getRange("Form Responses!C:C").getLastRow();i++){
// Checks to see if they are trying to add a new fox.
if (sheetMaster.getRange("Form Responses!C" + i.toString()).getValue() == "Register a New Fox"){
famTree.getRange("Family!B"+(6+registeredFoxes)).setValue(sheetMaster.getRange("Form Responses!D" + i.toString()).getValue());
famTree.getRange("Family!C"+(6+registeredFoxes)).setValue(sheetMaster.getRange("Form Responses!E" + i.toString()).getValue());
famTree.getRange("Family!D"+(6+registeredFoxes)).setValue(sheetMaster.getRange("Form Responses!F" + i.toString()).getValue());
famTree.getRange("Family!E"+(6+registeredFoxes)).setValue(sheetMaster.getRange("Form Responses!G" + i.toString()).getValue());
famTree.getRange("Family!H"+(6+registeredFoxes)).setValue(sheetMaster.getRange("Form Responses!H" + i.toString()).getValue());
registeredFoxes = registeredFoxes + 1;
// Checks to see if they are submiting a transaction.
}else if (sheetMaster.getRange("Form Responses!C" + i.toString()).getValue() == "Submit a Transaction"){
transaction.getRange("Transactions!E"+(8+transactionsMade)).setValue(sheetMaster.getRange("Form Responses!I" + i.toString()).getValue());
transaction.getRange("Transactions!F"+(8+transactionsMade)).setValue(sheetMaster.getRange("Form Responses!L" + i.toString()).getValue());
if (sheetMaster.getRange("Form Responses!J" + i.toString()).getValue() == "Profit"){
transaction.getRange("Transactions!C"+(8+transactionsMade)).setValue("+");
transaction.getRange("Transactions!D"+(8+transactionsMade)).setValue(sheetMaster.getRange("Form Responses!K" + i.toString()).getValue());
}else{
transaction.getRange("Transactions!C"+(8+transactionsMade)).setValue("-");
transaction.getRange("Transactions!D"+(8+transactionsMade)).setValue("-"+ sheetMaster.getRange("Form Responses!K" + i.toString()).getValue());
}
transaction.getRange("Transactions!B"+(8+transactionsMade)).setValue(sheetMaster.getRange("Form Responses!M" + i.toString()).getValue());
transactionsMade = transactionsMade + 1
}else{
debugSheet.getRange("Debug!A3").setValue(registeredFoxes);
}
}
famTree.getRange("Family!B2").setValue("Fox Family Members:\n" + registeredFoxes)
transaction.getRange("Transactions!B4").setValue("Transactions Made:\n" + transactionsMade)
}
The only way that you can speed things up is to get all the data out of all 3 sheet tabs, manipulate the data in the 2D arrays, and then set the new values all at once. Currently you are getting and setting values on every iteration of the loop, so there are read and write operations happening constantly which takes time. You need to read all the data once, and write all the data once.
The loop might be terminating for some reason. And the only reason it might be doing that is if the count hits the last row number, or there is an error in your code:
I would change:
for (i=2; i <= sheetMaster.getRange("Form Responses!C:C").getLastRow();i++){
To:
var L = sheetMaster.getRange("Form Responses!C:C").getLastRow();
Logger.log("L: " + L)
for (i=2; i<=L;i++){
This seems to work but it's not orthodox any adjustments that could be made let me know
Pretty much it was taking to much effort and memory to find the fields that I keep calling so I got all of them at once and then ran statements off that.
function SetCellData() {
var ss = SpreadsheetApp.getActiveSpreadsheet()
var debugSheet = ss.getSheetByName("Debug")
var family = ss.getSheetByName("Family")
var transaction = ss.getSheetByName("Transactions")
var sheetMaster = ss.getSheetByName("Form Responses")
// Starts from Row 2 Column C
var vA=sheetMaster.getRange(2, 3,(sheetMaster.getLastRow()-1),(sheetMaster.getLastColumn()-2)).getValues();
var lastColumn = sheetMaster.getLastColumn()
var registeredFoxes = 0
var transactionsMade = 0
var slotsScanned = 0
var L = sheetMaster.getRange("Form Responses!C:C").getLastRow();
Logger.log("L: " + L)
for (i=2; i<=L;i++){
// Checks to see if they are trying to add a new fox.
if (vA[i-2][0] == "Register a New Fox"){
for (j=1;j<=5;j++){
if (j==5){
family.getRange(registeredFoxes+6,j+3).setValue(vA[i-2][j]);
}else{
family.getRange(registeredFoxes+6,j+1).setValue(vA[i-2][j]);
}
}
registeredFoxes ++
// Checks to see if they are submiting a transaction.
}else if (vA[i-2][0] == "Submit a Transaction"){
for (j=7; j<=11;j++){
transaction.getRange(transactionsMade+8,j-5).setValue(vA[i-2][j-1]);
if (vA[i-2][j-1] == "Profit"){
transaction.getRange(transactionsMade+8,j-5).setValue("+");
}else if (vA[i-2][j-1] == "Loss"){
transaction.getRange(transactionsMade+8,j-5).setValue("-");
}else if (j == 9 && vA[i-2][7] == "Loss"){
transaction.getRange(transactionsMade+8,j-5).setValue("-" + vA[i-2][j-1]);
}
}
transactionsMade ++
}else{
Logger.log("Tried to read something...")
}
}
family.getRange(2,2).setValue("Fox Family Members:\n" + registeredFoxes);
transaction.getRange(4,2).setValue("Transactions Made:\n" + transactionsMade);
}
What i did:
If any cell in column M has been changed, then email notification has been sended to specify adres from column N at the same row. But i also need some specify body text from other columns at the same row. I did something that is working but it also causes that if other declared columns (such as project, customer, task, executor) has been changed the emil has been send to.
What i need:
Just track change in only one "M" column and put at the body of email additional data from other columns but from the same row. And (thats the point) did not track change at other columns, email should be send only if changing column M.
Probably it would be easy, but i'm twisted...
I bulid this script based on:
email notification if cell is changed
how to attach onChange cell value event/script to google sheet
Restrict notifications sent for changes referenced in columns
My script:
function sendNotification() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var cell = ss.getActiveCell().getA1Notation();
var row = sheet.getActiveRange().getRow();
var cellvalue = ss.getActiveCell().getValue().toString();
var sendto = '';
if(cell.indexOf('M')!=-1){
sendto = sheet.getRange('N'+ sheet.getActiveCell().getRowIndex()).getValue()
}
var project = '';
project = sheet.getRange('C'+ sheet.getActiveCell().getRowIndex()).getValue()
var customer = '';
customer = sheet.getRange('D'+ sheet.getActiveCell().getRowIndex()).getValue()
var task = '';
task = sheet.getRange('E'+ sheet.getActiveCell().getRowIndex()).getValue()
var executor = '';
executor = sheet.getRange('F'+ sheet.getActiveCell().getRowIndex()).getValue()
var deadline = '';
deadline = LanguageApp.translate(Utilities.formatDate(sheet.getRange('I'+ sheet.getActiveCell().getRowIndex()).getValue() , "GMT" , "EEEE, dd MMMM YYYY" ),'en','pl')
var status = '';
status = sheet.getRange('M'+ sheet.getActiveCell().getRowIndex()).getValue()
var mysubject = status + ' | ' + project + ': ' + task + ' - ' + ss.getName() + ' update';
var mybody = '\nStatus: ' + status + '\n\nproject: ' + project + '\ncustomer: ' + customer + '\ntask: ' + task + '\nexecutor: ' + executor + '\nDeadline: ' + deadline + '\n\n' + ss.getName() + ': \n' + ss.getUrl();
MailApp.sendEmail({
to:sendto,
subject:mysubject,
body:mybody});
};
I have made an example script that you should be able to see at https://docs.google.com/spreadsheets/d/11u0xkdtPlQsnVppCnPYM0CHuCTPLdmN8PcFlaW08lNw/edit#gid=0
You'll have to make a copy to actually do something with it.
But ... copying won't give you the trigger I set up. If you edit the script, go to the menu Reources --> Current project's triggers and make yourself a time-based trigger on the function checkForChanges(). I set it for "every minute" for testing purposes.
To your columns I added four new ones:
concat - is simply a concatenation of all the row values whose changes you need to monitor
Current Hash - is generated from a simple function I added to your script
Last Edit - after sending an email the script gives this cell the value of Current Hash
Changed - Compares Last Edit and Current Hash and says **true* if they are different.
So ... periodically the function checkForChanges() runs down the range ChangeDetector looking for true. If it finds nothing it quits immediately.
Each time it does find a change, it collects the data of that row and emails it. (Actually, I just log it, for simplicity sake.)
The key trick is the pair of lines :
lastEdits[row_][0] = currentHashCodes[row_][0];
ss_.getRangeByName("LastEdit").setValues(lastEdits);
Note how clear your code can be if you use named ranges where ever possible.
Here's the code in case the example gets lost someday in the future :
/* Called from cells in column "Current Hash" */
function strHash(valCell) {
var hash = 0;
if (valCell.length == 0) return hash;
for (i = 0; i < valCell.length; i++) {
char = valCell.charCodeAt(i);
hash = ((hash<<5)-hash)+char;
hash = hash & hash; // Convert to 32bit integer
}
return hash;
}
/* Called periodically from a timed trigger. */
function checkForChanges() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var changeDetector = ss.getRangeByName("ChangeDetector").getValues();
for (row in changeDetector) {
if (row > 0) {
changed = changeDetector[row][0];
if (changed) notify(ss, row);
}
}
}
/* Called by checkForChanges(). */
function notify(ss_, row_) {
initializeRangeArrays(ss_);
status = statii[row_][0];
project = projects[row_][0];
task = tasks[row_][0];
customer = customers[row_][0];
executor = executors[row_][0];
deadline = deadlines[row_][0];
sendto = recipients[row_][0];
var mysubject = status + ' | ' + project + ': ' + task + ' - ' + ss_.getName() + ' update';
var mybody = '\nStatus: ' + status
+ '\n\nproject: ' + project
+ '\ncustomer: ' + customer
+ '\ntask: ' + task
+ '\nexecutor: ' + executor
+ '\nDeadline: ' + deadline
+ '\n\n' + ss_.getName()
+ ': \n' + ss_.getUrl();
Logger.log("to: " + sendto);
Logger.log("subject: " + mysubject);
Logger.log("body: " + mybody);
Logger.log("");
lastEdits[row_][0] = currentHashCodes[row_][0];
ss_.getRangeByName("LastEdit").setValues(lastEdits);
};
var recipients = null;
var projects = null;
var customers = null;
var tasks = null;
var deadlines = null;
var executors = null;
var statii = null;
var lastEdits = null;
var currentHashCodes = null;
var rangeArraysInitialized = false;
/* Called by notify(). */
function initializeRangeArrays(ss_) {
if ( ! rangeArraysInitialized ) {
recipients = ss_.getRangeByName("Recipient").getValues();
projects = ss_.getRangeByName("Project").getValues();
customers = ss_.getRangeByName("Customer").getValues();
tasks = ss_.getRangeByName("Task").getValues();
deadlines = ss_.getRangeByName("Date").getValues();
statii = ss_.getRangeByName("Status").getValues();
executors = ss_.getRangeByName("Executor").getValues();
lastEdits = ss_.getRangeByName("LastEdit").getValues();
currentHashCodes = ss_.getRangeByName("CurrentHash").getValues();
rangeArraysInitialized = false;
}
}
Update 2014/09/22 :
I have made a few changes in the demo spreadsheet. Please take a peek.
To the script I added . . .
function strArrayHash(range) {
var ret = new Array();
var str = "";
for (item in range) {
str = range[item].toString();
if (str.length > 0) {
ret[item] = strHash(str);
Utilities.sleep(50); // play with this to reduce "internal execution error"s
} else {
ret[item] = "";
}
};
return ret;
}
In column "P" I replaced . . .
=if( M2 = "", "", strHash(R2)))
=if( M3 = "", "", strHash(R3)))
:
:
=if( M22 = "", "", strHash(R22)))
. . . with . . .
=ARRAYFORMULA(if( M2:M514 = "", "", strArrayHash(R2:R514)))
. . . in cell "P2" ONLY. I also deleted the contents of all cells in the range "P3:P22"
I increased the total number of rows to 1026
Once values appeared in column P, I copied cells P2:P1026 into M2:M1026 using [Paste Special] ยป [Paste values only].
In my i7 laptop it took it 30 seconds to recalc and detect a change in line 500.
I get an internal execution error if I try to do all 1024 lines.
Probably you will need to complicate the "if" clause such that it causes hash calculations on only the lines that really need it.
Update :: 2017/02/24 No one is interested in this so I stopped the trigger that ran the script.
I want to delete empty rows. Row 885 is a non-empty row and should be retained. However, my code says (and shows in the msgbox) that 885 is empty and should be deleted. When it gets to i=884, it prints what is really in row 885 and says it will NOT be deleted. So, I'm confused. It appears to be acting upon the data properly, yet it's reporting the row number wrong. I suspect you're going to tell me offsets are zero based and rows start at 1, so if lastrow=888 (1 based), then I need to subtract 1 to match the offset. so what I see as row 885 in the spreadsheet is really row 884 as an offset.
But... when i=row 885 has data, the offset is 884. And when i=884, the offset is 883... so why is it printing what's in row 885??? Seriously confused here. What am I getting wrong?
Last but most importantly... it's deleting the wrong row! How can it be referencing the right data, yet still deleting the wrong row???
var s = SpreadsheetApp.getActiveSheet();
var range = SpreadsheetApp.getActiveSheet().getDataRange();
var i = range.getLastRow();
var msg;
var colNum;
for (; i > 1; i--) { // I like to start at the end
var foundAvalue=0; // reset flag for each row
rowRange = range.offset(i, 0, 1);
var valArray=rowRange.getValues();
var foundAvalue=0;
var msg;
var totalColumns=rowRange.getNumColumns();
colNum=0;
while (colNum < totalColumns && (foundAvalue==0)) {
if (valArray[0][colNum] != '') {
foundAvalue=1;
msg="Row " + i + " =" + valArray[0];
Browser.msgBox(msg);
msg="Row " + i + " will not be deleted";
Browser.msgBox(msg);
}
colNum++;
}
if (foundAvalue == 0) {
msg="Row " + i + " =" + valArray[0] + "WILL be deleted";
Browser.msgBox(msg);
// delete empty row
// s.deleteRow(i);
}
} // end for(i)
Below is some code which should accomplish what you want. You are correct in that the root of the problem is in the 0-based indexing. The tricky part is that JavaScript/Apps Script arrays use 0-based indexing, but when you call something like getLastRow(), the returned range is 1-based. All-in-all, your code is good - it is only that issue that is tripping you up. Hope this helps:
function DeleteFun(){
var mySheet = SpreadsheetApp.getActiveSheet();
var range = mySheet.getDataRange();
// Iterate using a counter, starting at the last row and stopping at
// the header (assumes the header is in the first row)
for (i = mySheet.getLastRow() - 1; i > 0; i--) {
var foundAvalue = 0;
// Here we get the values for the current row and store in an array
rowRange = range.getValues()[i]
// Now we iterate through that array
for (j = 0; j <= rowRange.length; j++) {
// If any non-nulls are found, alert they won't be deleted and move on
if (rowRange[j] != null && rowRange[j] != '') {
foundAvalue = 1;
msg="Row " + (i+1) + " =" + rowRange[0] + " and will not be deleted";
Browser.msgBox(msg);
break;
}
}
if (foundAvalue == 0) {
msg="Row " + (i+1) + " =" + rowRange[0] + "WILL be deleted";
Browser.msgBox(msg);
// Delete empty row
mySheet.deleteRow(i+1);
}
}
}
You could do it in 'pure' array as well if you don't use formulas in your sheet.
Like this for example :
function deleteEmptyRows(){
var sh = SpreadsheetApp.getActiveSheet();
var data = sh.getDataRange().getValues();
var targetData = new Array();
for(n=0;n<data.length;++n){
if(data[n].join().replace(/,/g,'')!=''){ targetData.push(data[n])};// checks the whole row
Logger.log(data[n].join().replace(/,/g,''))
}
sh.getDataRange().clear(); // clear the whole sheet
sh.getRange(1,1,targetData.length,targetData[0].length).setValues(targetData);//write back all non empty rows
}
I want to do a variation on the "Sending emails from a Spreadsheet" tutorial on the Google Developers page.
I want to do exactly what they walk me through EXCEPT that when it comes to marking the cell in each row every time the e-mail is sent, I want to mark that on a SEPARATE sheet.
So, on the sheet "NeedReminders", my script finds the e-mail addresses of members to whom the e-mail should be sent in column A (which then becomes variable "emailAddress"). It sends the e-mail to that address.
THEN--and here's where I need help--I want the script to go to the "Master" sheet, find the row where emailAddress is in column X, and--in that same row--set the value of column BT.
Shouldn't be difficult. I just don't necessarily know how to do the "find the row where columm X equals emailAddress and set value of BT to today's date" part.
Who can help? Thanks in advance!
Here's my code so far (I'm always embarrassed to show my code because I'm sure it's terrible juvenile, so be gentle, please!):
function sendDuesReminder() {
var sheet = SpreadsheetApp.openById('___').getSheetByName('NeedReminders');
var startRow = 4; // There are a bunch of headers and other nonsense.
var valueSheet = SpreadsheetApp.openById('___').getSheetByName('Master');
// Determine how many rows to process
var rowRange = sheet.getRange(3,4); // On my sheet, cell D3 counts how many members are on the list of people who need a reminder e-mail.
var rowData = rowRange.getValue();
// End Determine how many rows to process
// Send e-mail
var dataRange = sheet.getRange(startRow, 1, rowData, 2);
var data = dataRange.getValues();
for (i in data) {
var row = data[i];
// var emailAddress = row[0]; //Column A: E-mail Address
var name = row[1]; //Column B: First Name
var subject = "Reminder About Dues for Newcomers & Neighbors";
MailApp.sendEmail("matt#twodoebs.com", subject, "Hi, " + name + ". It's been some time since we received your online registration for Newcomers & Neighbors. But we don't seem to have received your dues payment, yet. So we wanted to drop you a quick line and remind you about it." + "\n\n" + "If you think we should have received your payment by now or there's some kind of a mistake, please let us know by responding to this message." + "\n\n" + "Otherwise, please send a check for __ made payable to ___ to:" + "\n\n" + "___" + "\n" + "___" + "\n" + "___" + "\n" + "___" + "\n\n" + "Thanks! Please don't hesitate to reach out if you have any questions." + "\n" + "___" + "\n" + "___" + "\n\n" + "Best wishes," + "\n\n" + "Kati" + "\n" + "Membership Director",
{name:"___"});
// End Send E-Mail
// Set that an a-(Experimental)
var valueRange = valueSheet.getRange // Here's where I kind of break down . . . I need *something* here that searches the sheet for emailAddress
setValue(today()) //I'm also not sure that this is the right way to set the value as today's date
SpreadsheetApp.flush();
// End Set value
}
Browser.msgBox("OK. Reminder messages have been sent. doebtown rocks the house!")
}
If you're still struggling, try this:
var emails = valueSheet.getRange('X:X').getValues();
var targetRange = valueSheet.getRange('BT:BT');
var dates = targetRange.getValues();
for (var j=0; j<emails.length; ++j) {
if (emails[j][0] == emailAddress) {
dates[j][0] = new Date();
break;
}
}
targetRange.setValues(dates);