Is there a way to display values different based on date? - google-apps-script

I'm trying to grab data vertically and display it horizontally with a column in between each value.
Here is example data:
Here is what I'm trying to get it to do:
I've tried exhausting everything I currently know to figure this out but I can't seem to get it and I believe it's an easy problem to fix. I'm just not having much luck with my keywords in google.
ps: just noticed I had some columns hidden still. In this case lets pretend the columns in examples are A/B/C/D/E.

Summarizing identical dates on multiple rows into separate columns on the same row
I don't completely understand how you went from example 1 to example 2 but it is clear that you want to summarize multiple rows with the same date into multiple columns on one row and that's what this function is an example of.
I'm guessing that once you provide a better explanation of what your trying to accomplish and how it's supposed to work then you may get some answers that are a lot easier to figure out.
Anyway I offer this solution as a possibility that you can start with if you like. I'm using a key/value object to keep track of the number of identical dates and if the method dObj.hasOwnProperty() returns false then it also helps to identify new dates.
The Code:
function condenseDates(){
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName('sheet name');//you need to change this for the appropriate sheet name
var rg=sh.getDataRange();//get all data values including headers
var vA=rg.getValues();
var dObj={};//data object which is used to identify new new dates and keep track of the run columns for identical dates.
var d=0;//delete counter
for(var i=1;i<vA.length;i++){
if(dObj.hasOwnProperty(vA[i][0])) {//if it does not have this property then it is a new date
dObj[vA[i][0]]=Number(dObj[vA[i][0]])+1;//if it does have this property then increment the DObj value which keeps track of the run column it will go into
var rg=sh.getRange(1,2 + Number(dObj[vA[i][0]]));//this is the header column cell for this dObj column
if(rg.isBlank()){//if it is blank then write a new one
rg.setValue('Run' + dObj[vA[i][0]]);
}
sh.getRange(Number(dObj.currentRow),2 + Number(dObj[vA[i][0]])).setValue(vA[i][1]);//put value for this line in the appropriate column in the currentRow
sh.deleteRow(i-d+1);//delete this line
d++;//increment the delete counter
}else{//it is a new date
dObj[vA[i][0]]=1;
dObj['currentRow']=i-d+1;//Current Data Row
var rg=sh.getRange(1,3);
if(rg.isBlank()){//if header has no label in the first run column then write it
rg.setValue('Run' + 1);
}
sh.getRange(Number(dObj.currentRow),2 + Number(dObj[vA[i][0]])).setValue(vA[i][1]);//write data in the appropriate column in this case it is always column3
}
}
sh.deleteColumn(2);
}
The Example Spreadsheet before running the script:
The Example Spreadsheet after running the script:
The value in column 2 for each consecutive identical date is placed in the next column on the first row in which that date appears. At the end column2 is deleted.
Javascript Object Reference
Google Apps Script Documentation
SpreadsheetApp Documentation
I played around with this and this version doesn't required additional identical dates to be consecutive. It create a unique currentRow for each date and that current row is used for all remaining identical dates even if they occur after other remaining dates. ie the dates don't have to be sorted.
function condenseDates(){
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName('36');//you need to change this for the appropriate sheet name
var rg=sh.getDataRange();//get all data values including headers
var vA=rg.getValues();
var dObj={};//data object which is used to identify new new dates and keep track of the run columns for identical dates.
var d=0;//delete counter
for(var i=1;i<vA.length;i++){
if(dObj.hasOwnProperty(vA[i][0])) {//if it does not have this property then it is a new date
dObj[vA[i][0]]=Number(dObj[vA[i][0]])+1;//if it does have this property then increment the DObj value which keeps track of the run column it will go into
var rg=sh.getRange(1,2 + Number(dObj[vA[i][0]]));//this is the header column cell for this dObj column
if(rg.isBlank()){//if it is blank then write a new one
rg.setValue('Run' + dObj[vA[i][0]]);
}
sh.getRange(Number(dObj[vA[i][0].toString()+'currentRow']),2 + Number(dObj[vA[i][0]])).setValue(vA[i][1]);//put value for this line in the appropriate column in the currentRow
sh.deleteRow(i-d+1);//delete this line
d++;//increment the delete counter
}else{//it is a new date
dObj[vA[i][0]]=1;
dObj[vA[i][0].toString()+'currentRow']=i-d+1;//A unique Current Data Row for each date when additional identical dates will expand into additional columns.
var rg=sh.getRange(1,3);
if(rg.isBlank()){//if header has no label in the first run column then write it
rg.setValue('Run' + 1);
}
sh.getRange(Number(dObj[vA[i][0].toString()+'currentRow']),2 + Number(dObj[vA[i][0]])).setValue(vA[i][1]);//write data in the appropriate column in this case it is always column3
}
}
sh.deleteColumn(2);
}

Related

Data into rows and colums

I'm relatively new to this, I've written a function in Google Apps Script, getting data from an API.
The problem is that it's inserted into one single cell, instead of multiple rows and columns.
Can anybody help?
Tried googling examples
What I do
function spotpriser() {
var priser = 'api.energidataservice.dk/dataset/Elspotprices' + '?start=2022-07-01&end=2022-07-02' + '&sort=HourDK';
var response = UrlFetchApp.fetch(priser);
Logger.log(response);
var fact = response.getContentText();
var sheet = SpreadsheetApp.getActiveSheet();
sheet.getRange(1,1).setValue([fact]);
}
if you do getRange(1,1).setValue() it is normal that all the data get inserted into one cell only: the one define by the range (aka A1).
Instead what you probably want to do is to get a larger range, corresponding the dimensions of your retrieved data.
For example, let's say you retrieved 3 lines of data, and one line of data is supposed to be displayed on 2 columns.
You first need to create an array of size 5 (nb of rows) where each element will also be an array, of size 2 (nb of cols for each element).
let myArray = [
[elt1_col1_value, elt1_col2_value],
[elt2_col1_value, elt2_col2_value],
[elt3_col1_value, elt3_col2_value]
]
Then you can insert this array into the right number of cells, i.e defining the right range
sheet.getRange(1,1,myArray.length,myArray[0].length).setValues(myArray); // start at row 1, col 1, insert the right number of rows and cols based on the array
Note that I use setValues (with the s) to indicate that it writes in several cells.
Also make sure there is at least one element in myArray otherwise myArray[0].length will throw an error.
Documentation for getRange.

How do I detect the newest row in a specific column?

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

google sheets script to remove rows based on dates in multiple cells

I am working on an app to remove old content from a spreadsheet. I have found several posts that hide or delete sheets based on specific data, but it is always in a single cell. I need something that checks dates in three cells per row.
The sheet is populated by a form that asks users to enter three dates they want a bulletin to run in our video and print bulletin at the HS where I teach. I just want to remove old data, and found countless (well, maybe 6 or 8) examples of scripts that did something similar, so I grabbed one and tried to expand the logic statement to check columns C, D, and E (using index, of course).
It works, but removes any row with a date that is not today rather than dates in all three columns. I also need to have it count cells that are empty as qualifying as old.
So I tried the script suggested below by Cooper, which is a big advancement. It is still skipping rows with empty values, and in one case removes a row that I don't want to remove
In the image shown a form accepts input from teachers who submit bulletin content for a maximum of three dates. I've formatted the sheet to show old dates in red, today in green, and future dates in yellow. I want to delete rows that have all red or empty cells. A bandaid, however, would be to force them to pick a date for all three rather than allow non entries. But it would be nice to have the code recognize blank cells as qualifying.
[![Data Sheet for daily bulletin][1]][1]
Here's what eventually worked. I'm sure there are some smoother ways of doing this, but I actually grew a bit in my programming ability by working things out through the log first, then moving on once I had evidence that what I was doing worked.
/*
Here is the thinking:
I need to have a script that is run from Add-ons that checks each cell of a range
for dates. It can be done through conditional formatting bg colors, but that is
redundant.
The first thing is to create the functions that find and return row numbers that have all
old dates. In the initial build these can be returned to the log (Logger.log()) inside
of a for(loop).
'*/
function readRows() {
var sh=SpreadsheetApp.getActiveSheet();
var rg=sh.getDataRange();
var vA=rg.getValues();
var rowsDeleted=0;
var today=new Date().valueOf();
//cycle through rows. In each row check column values for date. if date is old, add 1 to oldCount
for(var i=vA.length-1;i>=0;i--) {
var oldCount=0;
if(new Date(vA[i][2]).valueOf()<today) {
oldCount++;
}
if(new Date(vA[i][3]).valueOf()<today) {
oldCount++;
}
if(new Date(vA[i][4]).valueOf()<today) {
oldCount++;
}
if(oldCount>2) {
sh.deleteRow(i+1);
rowsDeleted++;
}
}
Logger.log(rowsDeleted);
}
Next I want to figure out how to make something that will output the daily bulletin to a Doc or pdf.
Try this:
function readRows() {
var sh=SpreadsheetApp.getActiveSheet();
var rg=sh.getDataRange();
var vA=rg.getValues();
var rowsDeleted=0;
var today=new Date().valueOf();
for(var i=vA.length-1;i>=0;i--) {
if ((new Date(vA[i][2]).valueOf()<today)||(new Date(vA[i][3]).valueOf()<today)||(new Date(vA[i][4]).valueOf()<today) ){
sh.deleteRow(i+1);
rowsDeleted++;
}
}
Logger.log(rowsDeleted);
}
I'm not completely sure what you want but this might help. When your deleting rows you should start from the bottom.

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.

How to copy row ranges in Google docs?

I have a Spreadsheet, like excel on Google Docs. I am using both Mozilla Firefox and Google Chrome, whichever works. Almost all my columns have dropdown list validation(you know, each cell has a dropdown list to select, I hope I made it clear). I arranged them when I first created the spreadsheet, gave all the columns validation from ranges I created.
My problem is, whenever I add a new row, that row doesn't have any validations, all of them are gone. The old rows still have the validations.
So then, I set the validations every time I add a new row, one by one. This is frustrating. Some people also had the same problem, asked online, but no one answered.
When I copy an empty row with validations and paste it on the new row, it works fine. So, what I am saying is, can you help me write a script for it? Like copying 5 rows when I execute the script?
I am trying to study the scripts but I did nothing nothing so far. I think
var actSc = SpreadsheetApp.getActiveSpreadsheet();
var range = actSc.getRange("A1:B1");
This all I got from the examples I saw. I mean it. I got nothing.
If this copies the ranges of one cell, then I guess I should do it for all my columns.
But how do I put them in the new row? Is there something like setRange?
I could really use some help. This is driving me crazy and I really don't get this script thing.
What I mean by range is that I have ranges like "STATES" and it includes "NY,LA,CA" etc. This NY,LA,CA fills the dropdown list in the cells of that STATES column. I hope this getRange means this range.
Sorry about my English.
If I understand correctly, you want to script a function that will add new rows to a sheet and maintain the existing validations for your columns. This is certainly possible and not too difficult. One approach could be a "refresh validations" function that updates your entire sheet all at once, in the event that you want to reuse it in other sheets. First, though, it sounds like you could use a brief overview of the object classes you need to know about to do basic Google Apps Scripts:
SpreadsheetApp - Think of this class as the foundation of the Spreadsheet Service. It provides file I/O and functionality that is not tied to specific spreadsheets, per se, such as UI and the creation of Data Validation sets. It's the interface to all of your individual spreadsheet documents.
Spreadsheet - A spreadsheet document file, which can contain multiple Sheets. This is what gets created when you create a new Google Sheets document in Drive. Provides document-level functions, such as the ability to manage ownership, set permissions, access metadata, etc. There's some overlap with the Sheet class, so this one can seem like a bit of a mishmash.
Sheet - An individual sheet is what you normally think of as a spreadsheet: a set of rows and columns. Each Spreadsheet document can contain many, distinct Sheets. The Sheet class lets you modify the overall appearance of the sheet. You can freeze or hide rows, protect ranges of cells from being edited, add/delete rows and columns, etc. You can also get data about the sheet, such as the last row that has content or the maximum range of the whole sheet.
Range - Dropping down another level, we reach the Range object, which represents a certain rectangular area of cells. This can be as small as a single cell or as large as the whole sheet. It does not seem possible, however, for Ranges to represent discontiguous cells. This is where you had some trouble, because you treated the Range object as content that you could copy and paste in your sheet, which is understandable. But a Range isn't the data in the cells it represents. It's just an interface to those cells. If you want to access the data itself, you have to drop down to the bottom level of the hierarchy:
Value - The actual contents of your sheets are normal JavaScript values: strings, integers, Booleans, etc. that you can manipulate with the subset of JavaScript that Google Apps Script supports.
In order to do something with the values in your sheet, you first get the Range object from the Sheet (which you get from the SpreadsheetApp) and then get the values from the Range:
var values = SpreadsheetApp.getActiveSheet().getRange("A1:B1").getValues(); // returns [[]]
Note that getValues() returns a multi-dimensional array. As a representation of the values in your sheet, it looks like this:
// row 1 [[column A, column B, column C, column D, ...],
// row 2 [column A, column B, column C, column D, ...],
// row 3 [column A, column B, column C, column D, ...],
// row 4 [column A, column B, column C, column D, ...],
// row 5 [column A, column B, column C, column D, ...], ...]
So if the range A1:B1 is a range of one row and two columns, you can retrieve the values with A1 notation or by specifying the upper left row and column of the range, and the number of rows and number of columns you want to retrieve:
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getRange("A1:B1");
var range = sheet.getRange(1, 1, 1, 2); // row 1, column 1, 1 row, 2 columns
var values = range.getValues(); // returns [[50, 100]]
If the value in A1 is 50, and the value in B1 is 100, the last function above will return [[50, 100]]. You can access individual cell values directly, too:
var range = sheet.getRange("A1");
var value = range.getValue(); // returns 50
var cell = range.getCell().getValues(); // returns [[50]]
Obviously, you can set the values of ranges, too:
var range = sheet.getRange("A1:B2");
range.setValues([[50, 100]]);
range = sheet.getRange(1, 1); // same as sheet.getCell(1, 1)
range.setValue(50); // the value of A1, or row 1 column 1, is now 50
The next step is to figure out how the Data Validation class works. You create a Data Validation object using the Data Validation Builder, which lets you chain together a series of rules to apply to a range. You then set the range to that Data Validation rule set:
var stateList = ["AK", "AL", "AR", ...];
var rules = SpreadsheetApp.newDataValidation() // create a new Data Validation Builder object and use method chaining to add rules to it
.requireValueInList(stateList, true) // first param is the list of values to require, second is true if you want to display a drop down menu, false otherwise
.setAllowInvalid(false) // true if other values are allowed, false otherwise
.setHelpText("Enter a state") // help text when user hovers over the cell
.build();
range.setDataValidation(rules); // apply the rules to a range
Now you can insert rows and the rules should copy over into them automatically:
var lastRow = sheet.getLastRow(); // get the last row that contains any content
sheet.insertRowAfter(lastRow);
Or copy the rules and use them elsewhere:
var cell = sheet.getRange(1, 1, 1, 1);
var rule = sheet.getDataValidation(); // returns rule
var range = sheet.getRange("A1:B1");
var rules = range.getDataValidations(); // returns [[rules, rules]]
var lastRow = sheet.getLastRow(); // or sheet.getMaxRows()
range.setDataValidations(rules);
So you can very easily put these concepts together to write whatever sort of function you need to add rows, build validation rule sets, and add validations to new ranges of cells. You can do most of these things more concisely than I have here, but it sounds like you're looking for a more in-depth explanation. I hope it helps.
var sheetToUpdate = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
sheetToUpdate.insertRowAfter(sheetToUpdate.getLastRow());
var rangeToUpdate = sheetToUpdate.getRange(sheetToUpdate.getLastRow()+1,1,1,sheetToUpdate.getMaxColumns());
sheetToUpdate.getRange(sheetToUpdate.getLastRow(),1,1,sheetToUpdate.getMaxColumns()).copyTo(rangeToUpdate, {formatOnly:true});