I've been digging around on line for a couple of hours now to no avail. I'm trying to write a formula(which I assume is only possible through AppScript), that based on a certain date, will re-paste contents in a cell as values only.
The issue I've had with other scripts is that in my situation I have several different values that need to be checked. I've set up a continuously updating calendar that pulls from an array with listed dates for each item. However, in that array, the row gets moved into an archive once it hits todays date. Thus, they get deleted from the calendar too.
If there is another solution to this, like pasting based off conditional formatting(this would then keep the formulas that haven't found a value yet, and paste ones that have been found as values only), that would be great too.
Any help or advice would be greatly appreciated. If you need any other info, please ask.
EDIT:
The spreadsheet link is in comments. Attached are a before and after picture to help visualize whats going on here. Since the calendar is pulling from the active sheet, and rows on the active sheet go to "archive" when J2-Today() is -1(yesterday), those then disappear from the calendar. I've tried to make a mixed sheet with both archive and active, but I get duplicates as a frequent issue.
See code below:
function archiveRows() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var mainSheet = ss.getSheetByName("Sheet1");
var archiveSheet = ss.getSheetByName("Sheet2");
// last row should be on the data itself, same with column
var lastRow = mainSheet.getLastRow();
var lastCol = mainSheet.getLastColumn();
// set time to 0 so we can only compare dates
var dateToday = new Date().setHours(0, 0, 0, 0);
var range = mainSheet.getRange(1, 1, lastRow, lastCol);
var values = range.getValues();
// remove header to only process data
var header = values.shift();
var indices = [], difference = [];
// remove dates that are earlier than today
// collect rows and indices data when condition is not met
values.forEach(function (row, index) {
if (row[9] >= dateToday)
return true;
difference.push(row);
indices.push(index);
});
// remove the row with earlier date
// offset with 2 due to 0-indexing and header
indices.forEach(index => mainSheet.deleteRow(index + 2));
// all rows that were deleted are copied to archive sheet
difference.forEach(row => archiveSheet.appendRow(row));
}
Sample Data:
Output:
Note:
If you need to have this run on a daily basis, Time Driven trigger is your friend. Just have it set on a daily basis triggering archiveRows
Make sure that the main sheet only contains the data. (I see your sheet has rows containing 9AM values below. Make sure to clear those as the script will mistakenly include it on the processing)
Adjust date condition if needed.
Related
I have a sheet "Sheet1" where a user inputs data in cells A3:E3. The goal is to then move that data into "Sheet2" where column A4:A34 is a numeric value for the day of the month. I want to move the data inputted on "Sheet1" into "Sheet2" on the row that corresponds with the current day of the month. Then "Sheet1" would clear itself for the next day ahead.
I'll be honest I'm not great with functions and I'm mainly looking for help on where to look to find a solution. I've tried different code that I do know how to use like Query but the issue is it doesn't lock the value in place and as soon as the values are wiped in Sheet1 they are then removed via the query.
You will need to run a function on a time-driven trigger. The function can use Range.getValues() to read the data, get the current date's day number with const dayNumber = new Date().getDate(), look up the day number in Sheet2 with Range.getValues().flat().indexOf(dayNumber), write the data with Range.setValues() and finally clear the source range with Range.clearContent().
It is unclear what the purpose the day numbers in Sheet2 serve, because the data would in any case be written once a day. It would seem simpler to always append the most recent data at the first available row and include a timestamp.
For an example of how to do this, see the appendRowsToArchiveSheet script.
Since the task is rather simple, here is the code that does the work:
// add menu Scripts
function onOpen() {
SpreadsheetApp.getUi().createMenu('🔨 Scripts')
.addItem('✅ Copy to archive', 'copy_range_to_archive')
.addItem('❌ Erase', 'erase_range')
.addToUi();
}
// erase the range A3:E3
function erase_range() {
SpreadsheetApp.getActiveSpreadsheet()
.getSheets()[0]
.getRange("A3:E3")
.clearContent();
}
// copy the range A3:E3 to Sheet 2
function copy_range_to_archive() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const src_range = ss.getSheets()[0].getRange("A3:E3");
const data = src_range.getValues();
const dest_sheet = ss.getSheets()[1];
const today = new Date().getDate();
const dest_range = dest_sheet.getRange(3 + today, 2, 1, data[0].length);
dest_range.clearContent();
dest_range.setValues(data);
}
It makes the menu 'Scripts' and two command 'Copy to archive' (the range A3:E3) and 'Erase' (the same range).
I've heavily edited the original question I posted, as i have solved some of the issue myself. I'm now stuck on just one thing.
function payINVOICE() {
var ss = SpreadsheetApp.getActive();
var ds = SpreadsheetApp.openById("14imcEob2qIZbH6AjGYtf16MJxbnfkhQn1ae4jR-Nzq4");
var srcSheet = ss.getSheetByName("INVOICE_ENTRY");
var dstSheet = ds.getSheetByName("INVOICE_ENTRY");
var data_range = srcSheet.getRange('B4:J100');
var data_data = data_range.getValues();
var data_clean = data_data.filter(function (r) {return r[1]});
var clear_range = srcSheet.getRange('B4:I100');
var lr = dstSheet.getLastRow();
dstSheet.getRange(lr+1, 2,data_clean.length,9).setValues(data_clean);
clear_range.clear();
}
This code checks the range B4:J100 for a value in Column B.
If there is a value and the script is run, it copies those rows onto dstSheet.
My role is marking invoices as paid or not.
The dstSheet will already contain the data, which is pulled back into the srcSheet with a query. Column K is not part of the original query.
If I mark a row as "PAID" in column K on the srcSheet, I want the code to take the data_data variable and overwrite what is already in the dstSheet, so that the query then pulls the data back into srcSheet with column J then showing "PAID".
It means I can then change column K to "NOT PAID", run the script again and it will over-write the "PAID".
This makes better sense than my last post and I am so close to achieving what I need, just stuck on this last bit.
If you simply want to monitor the changes between the two mentioned sheets, it would be much easier to use an onEdit(e) trigger which will tell you which cell has been edited.
Snippet
function payINVOICE(e) {
var srcSheet = SpreadsheetApp.getActiveSheet(); //gets the active sheet which is supposed to be source sheet
var dstSheet = SpreadsheetApp.openById('DEST_SHEET_ID').getSheetByName('INVOICE_ENTRY'); //gets the destination sheet
if (e.range.getSheet().getName() == 'INVOICE_ENTRY' && e.range.getColumn() == 11) { //e specifies where the edit has been made - therefore this if condition checks if the edit is in the INVOICE ENTRY sheet and if the column is the K column
var row =e.range.getRow(); //this gathers the row at which the edit has been made
var data = srcSheet.getRange(row, 2, 1, 10).getValues(); //this gathers the data corresponding to the row at which the edit has been made
dstSheet.getRange(row, 2, 1, 10).setValues(data); //this sets the data into the corresponding row in the destination sheet
}
Explanation
The above code uses the onEdit(e) installable trigger and the e event object. In this way, when an edit is being made on the srcSheet on the 11th column (aka K column) and the sheet name is "INVOICE_ENTRY", then the row at which the change has been made is kept in the row variable. Afterwards, the corresponding row of data is kept in the data variable; the getRange(row, 2, 1, 10) references the range for the row at which the change on the K column has been made. In order to update the dstSheet, the data value is set to the according range using setValues(data).
Installing the trigger
To make the payINVOICE(e) function trigger on an edit action, you need to install an onEdit trigger.
This is being done by accessing the project's triggers by clicking this icon:
After that, you just need to create a new trigger by clicking the Add trigger button and create a trigger with the following settings:
Trying the function
In order to try the behavior for this, you just need to make an edit on the srcSheet on the K column and this change will be reflected in the destSheet.
Note
The ranges that have been used in this script are chosen considering the fact that:
K column consists of the PAID/NOT PAID text;
The srcSheet and the dstSheet have the data wanted in the same ranges.
You might need to customize these according to your sheet and add the needed formulas/filters you have mentioned.
Reference
Apps Script Installable Triggers;
Apps Script Event Objects.
I am learning coding in Sheets, and have already jigsawed together a couple of games (based off very limited coding knowledge. Very fun to see it come together
Next task:
I am logging my hours spent doing "Growth" things (like learning, exercise, work etc.) vs "Leisure" things (like TV, games, FB scrolling etc). Up until now I just scroll down to today's date and add in the numbers manually. Would be fun to have a button at the top of my sheet that just adds hours to the "Growth" or "Leisure" cells for today.
I have used formulae in the cell G371 to reveal the A1 notation of my target cell, hoping I can use this info in a script to write to that cell:
=substitute(Cell("address",vlookup(A1,A2:D367,3)),"$","")
Where cell A1 contains =today()
and A2:A367 are all the dates in the year.
and columns C and D hold numbers for growth and leisure respectively.
Then I tried this code to try to see how to write to a cell. I realise I am probably missing some very fundamental knowledge...
var ss = SpreadsheetApp.getActiveSpreadsheet();
var cellname = ss.getRange('G371') // makes cellname refer to G371
var input = cellname.getValue() // makes 'input' return the contents of G371, in today's case its 'C120'
var test = ss.getRange(input) // hopefully reads 'C120' instead of "input"
test.setValue(1) // setting value to 1 for now, as I'm just testing cell
// referencing. later I will work on figuring out the
// math for the buttons. I'm not there yet.
So it didn't work. apparently the problem is with ss.getRange(input) but I don't know what to do about that.
So my question is how to write to a cell that is named in another cell?
hope it makes sense
Thanks!
PS Here is a copy of the sheet
https://docs.google.com/spreadsheets/d/1KkoCb8kY1XICMeB9bx65HXsldCS-fa75xJCaU52WGg8/edit?usp=sharing
The formula in the cell might be messing with the value you retrieve. To make sure that does not happen, you can use getDisplayValue() instead of getValue().
Another way:
Also, if you just want to retrieve the cell where to write today's growth (or leisure), you don't need to follow this roundabout way of creating a formula in another cell to reference this one. You can do this instead (check comments):
function todayGrowthCell() {
var sheet = SpreadsheetApp.getActiveSheet();
var date = Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "d/M"); // Format today's date as in spreadsheet, in order to compare
var firstRow = 2; // Row where days start
var column = 3; // Column C (Growth)
var dates = sheet.getRange(2, 1, 366).getDisplayValues().map(row => row[0]); // Retrieve columns with dates (its display value, in order to compare with today's date, which is formatted accordingly)
var row = dates.indexOf(date) + firstRow; // Today's row
var cell = sheet.getRange(row, column); // Today's growth cell
cell.setValue(1); // Set value to today's growth cell
}
My goal is to automate adding 10 rows to a Google Sheet used by a nonprofit organization's business and then replicate the formulas sequences needed in the newly created rows. I had all the code working to accomplish the task and prevent users from messing up the spreadsheet formulas when they manually insert rows. However, the code time out due to the number of rows in the spreadsheet with the looping use of getRange(). My new approach is to jump to a named cell as a starting point instead of the the really slow looping cell search.
I have created a name "EndData", read all the stuff I can find online, trialed and errored the syntax for hours to get the named_cell range into myrange and then activate the range on the worksheet...
Here is the current coding attempt (which leaves the cursor at the top of the column and an
"TypeError: Cannot find function getRangeByName in object Sheet. (line 170, file "macros")"
//Get EOD range, select, index up 3 rows to start row insertions
function getEOD() {
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
Logger.log(ss); //lOG is not helpful, says, "sheet", not SheetName
var MyRange = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getRangeByName("EndData");
Logger.log(MyRange); //lOG is not helpful, says, "Range", not RangeAddress
//Activate the named cell, moves with the spreadsheet
MyRange.activate();
};
Had a new idea after I asked for help, here's the working code that gets the job done:
//Get EOD range, select, index up 3 rows to start row insertions
function getEOD() {
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
//Logger.log(ss); //lOG is not helpful, says, "sheet", not SheetName
var rg = SpreadsheetApp.getActiveSpreadsheet().getRangeByName('EndData');
if (rg != null) {
Logger.log(rg.getNumColumns());
}
//Logger.log(rg); //lOG is not helpful, says, "Range", not RangeAddress
//Referenced the Named the EOD cell
//Future, Trying to create a debug status bar
//SpreadsheetApp.getActiveSpreadsheet().toast("looking at row" & i "now")
//Activate the named cell, which moves with spreadsheet growth, down
rg.activate();
//Uncomment for testing purposes, places a Yes on the row 4 columns over
//sheet.setActiveRange(sheet.getRange(0, 4)).setValue('Yes');
//turned off durning testing, writes in data range with this trial code
//Reposition from named cell to insert lines location
ss.getRange(sheet.getCurrentCell().getRow() -2, 1, 1,
sheet.getMaxColumns()).activate();
//Insert ten lines, copy and paste special formulas only
Insert_10_Lines()
//https://stackoverflow.com/questions/59772934/get-range-from-
getrangebyname-activate-range
};
My answer is I have tenacity, I try to limit the variables one at a time to prove how things really work, moved the code that did work in the test script file to a production script file and it didn't work for copy of what I had in test, went back to the test script and it didn't work either, after it had... There are variables in play that seem to be happening at the scripting, savings and running steps that change the interactive responses I am getting. Trial and error again through the production environment came up with a working combination by going to the simples code suggested and getting a combination that works. Here is the the code that is running in product to the main need of my question...
function InsertRows() {
var spreadsheet = SpreadsheetApp.getActive();
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
//Get range from named cell
var range = SpreadsheetApp.getActiveSpreadsheet().getRangeByName("EOD");
if (range != null) {
Logger.log(range.getNumColumns());
}
//Select the cell as a starting point for code to follow
range.activate(); //start location cell is active and row is selected
Thanks for those that responded for the help! Looks like it is going to take a while to recognize the patterns and figure out what to do to get consistent results in a timely manner...
Try this:
function getEOD() {
var ss=SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var MyRange=ss.getRangeByName("EndData");
MyRange.activate();
}
I'm working on a large sheet and cells are precious given gsheets quota.
I have a range, and when data updates automatically a script updates the named range automatically to be the full length of the data.
The named range is called "gadatapull". This range is on the tab "datapull".
Two tabs. "datapull" is where fresh data is dumped and "data_prep" is where I do stuff to the data. After a fresh pull just now datapull has 2,733 rows of data, including the headers.
I would like data_prep to have the same length as datapull. Plus 7 rows for text at top of data_prep.
When my script to update data runs I do this:
// clear dataprep sheet for new data
var lastRow = 7;
var maxRows = dataprep.getLastRow();
if(maxRows - lastRow > 0) {
dataprep.deleteRows(lastRow+1, maxRows-lastRow);
}
data_prep has 7 rows (because the script just deleted all rows above 7).
Now, in data_prep cell A7 I have:
=query(indirect("gadatapull"),"select *")
Expected result was that all the fresh data in "gadatapull" would appear in data_prep tab and that data_prep tab would expand accordingly.
But what actually happens is all the data arrive as expected, but then there are an additional blank 500 rows at the bottom. This 500 number is too rounded off. Makes me think Gsheets is automatically adding this number as a default under some condition.
How can I prevent Gsheets from adding these additional 500 rows?
Instead of letting the sheet API expanding the number of rows (which is your hypothesis and might well be true ;-) you can add all the necessary cells before importing data.
I didn't try in real conditions but this should work.
Btw, the script imports data as well.
code :
function copyDataToSheet(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var dataprep = ss.getSheetByName('data_prep');
var datapull = ss.getSheetByName('datapull');
var lastRow = 7;
var maxRows = dataprep.getLastRow();
if(maxRows - lastRow > 0) {
dataprep.deleteRows(lastRow+1, maxRows-lastRow);
}
var datapullSize = datapull.getLastRow();
dataprep.insertRows(7,datapullSize);// insert exactly the number of rows you need.
var dataToCopy = datapull.getDataRange().getValues()
dataprep.getRange(7,1,dataToCopy.length,dataToCopy[0].length).setValues(dataToCopy);
}