Jump to current date cell (dates are in one row) Google Sheets - javascript

I have a google sheets spreadsheet. Row 2 contains dates e.g. 25/08/2020, 26/08/2020 going across many columns. Is there a script I can run to make it jump to the cell containing the current date when the document is first opened?
I know there is OnOpen() method which you define and it runs on opening the document, however, it is getting the code that actually works that's proving difficult.
Note: I have looked at Google spreadsheet / docs , jump to current date cell on Open but the solutions don't work (I assume due to me having my dates all in one row).
I don't know javascript really well, I understand a little of the basics. Any help would be much appreciated.
Thanks

The code you found at Google spreadsheet / docs , jump to current date cell on Open does not work for you as it only checks the first column.
I modified this code a little to search for dates on a row. Change rowWithDates variable as needed.
function onOpen() { // runs automatically
var rowWithDates = 2; // change as needed
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getActiveSheet();
var range = sh.getDataRange()
var data = range.getValues();
var today = new Date().setHours(0,0,0,0);
var diffref = today;
var diff;
var idx;
for(var n=0;n<range.getWidth();n++){
var date = new Date(data[rowWithDates-1][n]).setHours(0,0,0,0);
diff=today-date;
if(diff==0){break}
Logger.log("diffref = "+diffref+" today-date = diff = "+diff);
if(diff < diffref && diff > 0){idx=n ; diffref=diff}
}
if(n==data.length){n=idx}
n++;
sh.getRange(rowWithDates, n).activate();
}

You can use the code that was provided in the answer you cited in your question, you just need to change a couple of things:
Make it look in a row, rather than a column (note that the data array is changing the second array dimension, rather than the first); and
Make it look in a specific row, rather than just hardcoded to the first (you could just, instead of 0, have the array use a variable "row"; instead, I just had the code pull the data for only the row with dates - this is faster for very large spreadsheets).
function onOpen() {
var row = 8; //set this to be the row with the dates
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getActiveSheet();
var data = sh.getDataRange();
var datesrow = sh.getRange(row,data.getColumn(),row,data.getWidth()).getValues();
var today = new Date().setHours(0,0,0,0);
for(var n=0;n<datesrow[0].length;n++){
var date = new Date(datesrow[0][n]).setHours(0,0,0,0);
if(date==today){break};
}
console.log(n);
n++;
sh.getRange(row,n).activate();
}

Solution:
While the other solutions might work for now, when working with Dates is recommended to consider display values instead. It is also highly recommended to get rid of old for loops and var declarations.
This will be a more futureproof solution:
function onOpen() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sh =ss.getActiveSheet();
const today = new Date();
const today_year= today.getFullYear();
const today_month = addZero(today.getMonth()+1);
const today_day = addZero(today.getDate());
const today_date = today_day.toString() + "/" + today_month.toString() + "/" + today_year.toString();
function addZero(i) {
if (i < 10) {
i = "0" + i;
}
return i;
}
const dates = sh.getRange(2,1,1,sh.getLastColumn()).getDisplayValues().flat(1);
dates.forEach((d,index)=>{
if (d===today_date){
sh.getRange(2,index+1).activate();}});
}
References:
getDisplayValues()

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

I want to create a script for google sheets that will make multiple copies of a sheet and place the new sheets to the left of the active sheet

I am very new to using google scripts. I have a spreadsheet with tabs that are titled by date. I found the following script that will enable me to create a variable number of duplicate sheets. The script will also automatically rename the new sheets by changing the date to the next date. The only problem is that all of the new sheets are added to the far right of the document. I want each copy to be placed to the left of the active sheet. So if I have a tab titled 1/3/21 and I make 3 copies of that tab, the new tabs will read from left to right 1/6/21; 1/5/21; 1/4/21. This is the script I am using. I had to disable "Enable Chrome V8 runtime" for this script to work.
function duplicatesheet() {
var as = SpreadsheetApp.getActiveSpreadsheet(); // active spreadsheet
var s = as.getActiveSheet(); // first sheet object
var dateCell = "A6:N6"; // cell containing first date
var N = 5; // number of copies to make
var startDate = new Date(s.getRange(dateCell).getValue()); // get the date stored in dateCell
var day = startDate.getDate(); // extract the day
var month = startDate.getMonth(); // extract the month
var year = startDate.getFullYear(); // extract the year
// loop over N times
for (var i = 0; i < N; i++) {
var asn = s.copyTo(as); // make a duplicate of the first sheet
var thisSheetDate = new Date(year, month, day+(i+1)); // store the new date as a variable temporarily
asn.getRange(dateCell).setValue(thisSheetDate); // writes the date in cell "B3"
asn.setName(Utilities.formatDate(thisSheetDate, undefined, "MM/dd/yy")); // sets the name of the new sheet
}
}
Explanation:
As the other answer explained you can use insertSheet(sheetName, sheetIndex, options). This answer explains how you can incorporate that function into your code but also how to make your code faster and more efficient.
In your code dateCell is a range of cells "A6:N6", but you only need a single value and that is A6. Therefore simply, change it to A6.
You don't need to create a date object of your value. If A6 in your sheet contains a date value, then GAS will pick it up correctly.
You don't need to build your own date objects iteratively. You can use:
startDate.setDate(startDate.getDate()+1);
and that will increase the date by one day. In this way you can get rid of day, month and year variables.
I am not sure why you put undefined as the second argument of Utilities.formatDate and also not sure why your script editor didn't drop an error. But you probably wanted to use as.getSpreadsheetTimeZone() instead.
To correctly insert the sheets to the left of the active sheet inside the for loop you need an index expression like that sindex-N+1. The +1 is used because getIndex starts counting from 1. Namely for the first sheet in your document getIndex would return 1. But the sheetIndex parameter in insertSheet accepts an index that starts from 0.
Solution:
function duplicatesheet() {
var as = SpreadsheetApp.getActiveSpreadsheet();
var s = as.getActiveSheet();
var sindex = s.getIndex();
var dateCell = "A6";
var N = 2;
var startDate = s.getRange(dateCell).getValue();
// loop over N times
for (var i = 0; i < N; i++) {
startDate.setDate(startDate.getDate()+1);
var startDateStr = Utilities.formatDate(startDate, as.getSpreadsheetTimeZone(), "MM/dd/yy")
var asn=as.insertSheet(startDateStr, sindex-N+1, {template: s});
asn.getRange(dateCell).setValue(startDate);
}
}
This seems kind of easy to do:
function myFunction() {
let ss = SpreadsheetApp.getActiveSpreadsheet();
let sheet = SpreadsheetApp.getActiveSheet();
let index = sheet.getIndex();
ss.insertSheet("New Sheet", index - 1, {template: sheet, });
}
After executing this code your sheet should look like this after being executed:
Reference
Selection of sheet in Apps Script
insertSheet(sheetName, sheetIndex, options)

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);

How to optimise Apps Script code by using arrays to pull data from Google Sheets and format it?

I have a script that takes data from a gsheet and replaces placeholders on gdoc. I am looking to optimise the script by using arrays instead.
This is a sample of my gsheet (the original gsheet spans 1000+ rows and 15+ columns),
Original script:
function generategdoc() {
SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1").activate();
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var lr = ss.getLastRow();
for (var i =2;i<=lr;i++){
if(ss.getRange(i, 1).getValue()){
//Make a copy of the template file
var documentId = DriveApp.getFileById('FileID').makeCopy().getId();
var Client = ss.getRange(i, 2).getValue();
var Amount = ss.getRange(i, 3).getValue();
var AmountFormat = Amount.toFixed(2).replace(/(\d)(?=(\d{3})+\.)/g, '$1,');
var Date = ss.getRange(i, 4).getValue();
var temp = new Date(Date)
var DateFormat = Utilities.formatDate(temp, "GMT+0400", "dd MMM yyyy")
//Rename the copied file
DriveApp.getFileById(documentId).setName(Client);
//Get the document body as a variable
var body = DocumentApp.openById(documentId).getBody();
body.replaceText('##Client##', Client).replaceText('##Amount##', AmountFormat).replaceText('##Date##', DateFormat)
}
else {}
}
}
As you can see this script will only run for all the rows which have been checkboxed TRUE.
Attempt 1 at optimising:
function optimise() {
SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1").activate();
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var lr = ss.getLastRow();
var rng = ss.getRange("A1:"+"D"+lr).getValues(); //Creation of Array
for (var i =2;i<=lr;i++){
if(ss.getRange(i, 1).getValue()){
var Client = rng[i-1][1];
var Amount = rng[i-1][2];
var Date = rng[i-1][3];
var documentId = DriveApp.getFileById('FileID').makeCopy().getId();
DriveApp.getFileById(documentId).setName(Client);
var body = DocumentApp.openById(documentId).getBody();
body.replaceText('##Client##', Client).replaceText('##Amount##', Amount).replaceText('##Date##', Date)
}
else {}
}
}
Question:
I was able to format the original script for Amount and Date. How can I have the same formatting for arrays? As I cant apply formatDate(class utilities) and toFixed to my variables anymore because they are now arrays.
I believe you can optimize your code by using the appropriate method(s) to preserve the text format of your Google sheet values.
Look into using the following method(s) of class Range from SpreadsheetApp service.
Method getDisplayValue() returns the displayed value of the cell in the range. The value is a String. The displayed value takes into account date, time and currency formatting, including formats applied automatically by the spreadsheet's locale setting. Empty cells return an empty string.
This should optimize your code by removing the necessity of regex. If I didn't understand your issue correctly, please leave a comment.
References:
getDisplayValue()
getDisplayValues()
getRichTextValue()
getRichTextValues()
You can try something like this. But it may not make any difference because creating files takes a long time.
function optimise() {
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var vA= ss.getRange(1,1,ss.getLastRow(),4).getValues();
var file = DriveApp.getFileById('FileID');
for(var i=0;i<vA.length;i++){
if(vA[i][0]){
var Client=vA[i][1];
var Amount=vA[i][2];
var Date=vA[i][3];
DriveApp.getFileById(file.makeCopy().getId()).setName(Client);
var body=DocumentApp.openById(documentId).getBody();
body.replaceText('##Client##', Client).replaceText('##Amount##', Amount).replaceText('##Date##', Date)
}
}
}
I wonder if you need to saveAndClose() the document.

Categories

Resources