How do I detect the newest row in a specific column? - google-apps-script

I am currently having some problems with my Google App Script.
What we have for now is: when the users input two values(time) into the column, the duration between the column will be automatically calculated in another column, which we did this by the functions in the Spreadsheet itself.
When we transfer the input values, we are using the function appendRow, and since things will get messed up in the column we set the value that is supposed to go in the duration column as null, so that it will be filled blank and that the function in the Spreadsheet itself will calculate it. However, the problem we encountered was that by using this function in the Spreadsheet, we get a 00:00 as a default value in every single row that doesn't have a time input, meaning that the appendRow function will detect these rows as filled, and will append the values to the very bottom of the Spreadsheet, creating a big gap between the rows with the values and without the values.
We need a solution to solve this issue, and we are posting to get any possible solutions about this.
So the method we thought of to solve this issue was using getRange(), then setValue. With getRange we thought of using something like make a 1x1 grid that has the row Index as the same row Index as the new cell in the specific column that we want to import the values, and for columns we can just choose the column Index. But we soon realized that this will still fail as appendRow in the row Index will still return the row index of the very last row in the spreadsheet.
The next method we tried was to detect any "0:00:00" in the duration column, then get the corresponding row Index, and use that row Index to setValue at that specific row using getRange(), which again, failed.
for (var i = 0; i < sheet.getMaxRows()+1; i++ ){
if (sheet.getRange(i,4).getValue() === "0:00:00") {
sheet.getRange(i,1).setValue(datas.enterDate);
sheet.getRange(i,2).setValue(datas.enterStartTime);
sheet.getRange(i,3).setValue(datas.enterEndTime);
sheet.getRange(i,6).setValue(datas.enterFullName);
sheet.getRange(i,7).setValue(datas.enterCategory);
sheet.getRange(i,8).setValue(datas.enterActivity);
sheet.getRange(i,9).setValue(datas.enterLink);
break;
}
}
Expected results: As the function goes down the column it will search for any cells containing "0:00:00", and when it does it will set the values with the users data.
Output: Nothing shown.

Try this:
col is the column number. The default is the column of the active cell
sh is a Sheet Obj and the the default is current active sheet
ss is a Spreadsheet Object and the default is the current active spreadsheet.
function newestRow(col,sh,ss){
var ss=ss || SpreadsheetApp.getActive();
var sh=sh || ss.getActiveSheet();
var col=col || sh.getActiveCell().getColumn();
var rg=sh.getRange(1,col,sh.getLastRow(),1);
var vA=rg.getValues();
while(vA[vA.length-1][0].length==0){
vA.splice(vA.length-1,1);
}
return vA.length+1;
}

Related

Google Sheet Macro - how do I return a column number based on dynamic cell contents?

I have the following code as a starting point. I want to select the entire column where row 3 contains a specific date value (it will be the date of the previous Monday; I have a formula returning this date in cell E1).
function selectDate() {
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
ss.getRange(3,?,1,ss.getMaxColumns()).activate();
}
Basically, the getRange column value would be interpreted as something like: "Find the column number where the value in row 3 is equal to the value in cell E1".
Any ideas would be very helpful, even if it's using a totally different method to achieve the same thing. Thank you so much!
In your situation, how about the following sample script?
Sample script:
function selectDate() {
var sheet = SpreadsheetApp.getActiveSheet();
var searchValue = sheet.getRange("E1").getDisplayValue();
var res = sheet.getRange(3, 1, 1, sheet.getLastColumn()).createTextFinder(searchValue).matchEntireCell(true).findNext();
if (!res) return;
var column = res.getColumn();
sheet.getRange(1, column, sheet.getLastRow()).activate(); // Here, the found column is activated.
Browser.msgBox("Found column number is " + column); // Here, the found column number is shown in a dialog.
}
From your situation, I thought that getRange(3,?,1,ss.getMaxColumns()) in your script might be getRange(3, 1, 1, sheet.getLastColumn()).
When this script is run, row 3 is searched using the value of cell "E1". When the value is found, as a sample, the found column is activated and the column number is shown in a dialog. This is a sample. Please modify this for your actual situation.
Note:
If no column is selected, it is considered that the value of cell "E1" is not found in row 3. At that time, can you provide the detail of your Spreadsheet? By this, I would like to modify it.
Reference:
createTextFinder(findText) of Class Range

Changing info on a different sheet in the same spreadsheet

I have two ranges of equal size on different sheets in the same spreadsheet. I am trying to find a row (based off of user input) in the first sheet and then use that index to modify a table in the second sheet that counts how many times that certain index has been used before (to make a nice looking pie chart).
This code runs but will not produce results on the second sheet. I've gone through the debugging process and my best guess is that for some reason, my for in loop is not running through. Attached is my code that takes in the beforementioned index and attempts to perform the second half of my goal.
function acceptToEncounterChart(ghostrow) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
SpreadsheetApp.setActiveSheet(ss.getSheets()[1]);
ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Average Encounter Chart");
var range = sheet.getRange("B3:B14")
for(var i in range) {
if(ghostrow == i) {
var before = range[i][0].getValue()
range[i][0].setValue(before + 1);
}
}
SpreadsheetApp.setActiveSheet(ss.getSheets()[0]);
};
Explanation:
I am not entirely sure what is your goal.
However, here is some fixes / improvements starting from the beginning:
You define 2 times the same variable ss with exactly the same value.
You don't need to set the active sheet, if your goal is to just get the sheet, therefore this line is redundant:
SpreadsheetApp.setActiveSheet(ss.getSheets()[1]);
Variable range is not an array but a range object. You can't index it and therefore you can't also use a for loop to iterate over a single object. For the same exact reason, the code inside the if statement is wrong, you can't index range. But you don't see any errors because the if statement evaluates to false.
In JavaScript and in many other programming languages, array indexes start from 0. Since your range starts from cell B3 or row 3, you need to use i+3 to match the data with the range.
For the same reason as the previous point, ghostrow is an index, not a row. The if statement compares an array index i with ghostrow, so ghostrow should not be confused with the actual sheet row. For example, if you choose ghostrow=5 then the current script will increment the value of the cell B8 (remember i+3) by 1.
Solution:
Here is a workable code snippet:
function acceptToEncounterChart(ghostrow) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Average Encounter Chart");
var data = sheet.getRange("B3:B14").getValues().flat();
data.forEach((v,i)=>{
if(ghostrow == i){
sheet.getRange(i+3,2).setValue(v+1)
}
});
ss.setActiveSheet(ss.getSheets()[0]);
}
Related:
Please explore the official google apps script documentation.

Auto fill a column with formulas when new rows are added

I have an automation workflow (Zapier) which adds a new row to a Google Sheets whenever a new card is added in Trello. We use it to track statistics for departments etc. I have some extra fields which transform a date field into things like weeknumber, day, month, year... simple stuff.
I need to write a script which looks out for new rows entered into the spreadsheet and then auto-fills the other columns with preset formulas.
Example layout:
Columns populated via the automation:
a,b,d,e
Columns to populate via the script.
f,g,h,i,j,k
I've seen a few scripts that are similar but don't take a formula from the row above as the content to autofill.
Note: The formulas for each column are available in the row above which has been added manually by me (for now). So, in theory, the script could reference the cell above for the correct formula to use.
Note 2: I cannot use the ARRAYFORMULA method because Zapier will see the row as having content and will skip to the next empty row.
I think this will do it.
function fillInFGHIJK()
{
var ss=SpreadsheetApp.getActive();
var sh=ss.getActiveSheet();
var rg=sh.getDataRange();
var vA=rg.getValues();
for(var i=0;i<vA.length;i++)
{
if(!vA[i][5] && !vA[i][6] && !vA[i][7] && !vA[i][8] && !vA[i][9] && !vA[i][10])
{
sh.getRange(i,6,1,6).copyTo(sh.getRange(i+1,6,1,6));//According to Michelle this will works better because the cell references change appropriately
}
}
}

Google Sheets OnEdit script: copy value from specific columns

We are using Google sheets for tracking attendance. Previously, the teachers were entering P, T, or A (for present, tardy, absent) for each period. I would still like users to have the option to enter a value for each period in a week, however it would be a great time saver if they could enter one value for the whole day.
What I'd like is that if a value is entered into any one of the "0" periods (green columns) with a "P" or "A" (data validation limits those options) an OnEdit function would copy that same letter ("P" or "A") to the following 8 columns and then delete the original value. (without the deletion the totals on the far right columns will be off). I would not want the OnEdit to be activitated based on edits in any of the non-green columns.
I will eventually have several tabs, each one a different week, but each exactly the same... so I'm thinking the function should work within whatever the activesheet is.
https://docs.google.com/spreadsheets/d/1NKIdNY4k66r0zhJeFv8jYYoIwuTq0tCWlWin5GO_YtM/edit?usp=sharing
Thank you for your help,
I wrote some code to get you started with your project. (I am also a teacher) You will have to make some changes based on what you are going for and it can probably be optimised to run faster. Good luck!
function onEdit(e) {
//create an array of the columns that will be affected
var allColumns = [2, 10];
//get the number values of the column and row
var col = e.range.getColumn();
var row = e.range.getRow();
//get the A1 notation of the editted cell for clearing it out
var cell = e.range.getA1Notation();
//only run if the cell is in a column in the allColumns array
if(allColumns.indexOf(col) > -1) {
//run the for loop for the next 8 cells
for(var i = col + 1; i < col + 9; i++) {
SpreadsheetApp.getActiveSheet().getRange(row, i).setValue(e.value);
SpreadsheetApp.getActiveSheet().getRange(cell).setValue('');
}
}
}

Finding column number of a cell with specific value

I currently have a code that can get the row number of a cell that contains a specific string. Here's an example of the code:
var ss = SpreadsheetApp.getActiveSheet();
var values = ss.getRange("B:B").getValues();
var i=j=firstrow=lastrow=0;
for(i=0;i<values.length;i++)
for(j=0;j<values[i].length;j++) {
if(values[i][j]==5100) firstrow=i+1;
else if(values[i][j]=='EOL') lastrow=i-2;
}
I was wondering if it's possible to do something like above, but for columns, that way my script will not fall apart if an user accidentally move a column.
So, what are doing is using .getRange("B:B") to define that you want all rows in column B. Then using getValues() to return all of those rows as a multidimensional array(obviously this will only have one column - so you probably don't need that other for loop).
So instead you can just use .getRange(row, column) (where row and column are integers greater than 1), this way you can go through the spreadsheet one item at a time using getValue(). So you could initially look through the first row to find the column index you are after, and then look down the rows to find the data you require.
Something like this might work for you:
var ss = SpreadsheetApp.getActiveSheet();
var valueColumn;
for(i=1;i<ss.getLastColumn();i++) {
if (ss.getRange(1,i).getValue() == "ColumnName") {
valueColumn = i;
break;
}
}
//At this point I assume that we have a value in valueColumn, if not this won't work.
var values = ss.getRange(2, valueColumn, ss.getLastRow()).getValues();
var i=firstrow=lastrow=0;
for(i=0;i<values.length;i++) {
if(values[i][0]==5100) firstrow=i+1;
else if(values[i][0]=='EOL') lastrow=i-2;
}
One thing to keep in mind is that arrays are 0 based where as getRange(row,column[,rows]) is 1 based.
My spreadsheet is only small, so speed impacts of doing one call and getting all data is minimal, but if you are actually using a large sheet you might find one method works faster than another.