Edit a column in google sheets based on a condition - google-apps-script

I've a Google sheet which contains two columns first is "Status" column and second is "RegDate" column. I want to update values in the "Status" column if Today's date matches with the RegDate column. Here, I am attaching a sample google sheet for reference.
Basically, I want to write an automated script which will run daily at a certain time and check if the date on which script is running matches with the date present in RegDate column then it should change the corresponding values in "Status" column.
For Eg. If the script is running on 3 Aug 2022 then the row where RegDate is 03-08-2022 should be considered and then "Inactive" value in Status column should be updated to "Active".
I do not have any idea how to automate this, as far as I've researched I need to write a Google AppScript for the same and use a time-driven trigger to run the script daily (Correct me if I am wrong here).
I want to know how can I proceed with this code any help on this would be appreciated. Please also mention any threads which can help me in solving this.

Solution:
Create a daily trigger. You can do this manually (following these steps), or programmatically (executing the function installTrigger below once).
The daily trigger will cause a specific function to execute once daily. On this function, iterate through your range, and for each row, check whether column B date is the same one as today. If that's the case, change column A value to Active.
function updateStatus() {
const now = new Date();
const day = now.getDate();
const month = now.getMonth();
const year = now.getFullYear();
const sheet = SpreadsheetApp.getActive().getSheetByName("Sheet1"); // Change according to your preferences
const range = sheet.getRange("A2:B" + sheet.getLastRow());
const updatedVals = range.getValues().map(row => {
const date = row[1];
if (typeof date.getMonth === 'function' && date.getDate() === day && date.getMonth() === month && date.getFullYear() === year) {
row[0] = "Active";
} else {
// row[0] = "Inactive"; // Uncomment if you want to change status from 'Active' to 'Inactive' if date is not today
}
return row;
});
range.setValues(updatedVals);
}
function installTrigger() {
ScriptApp.newTrigger("updateStatus")
.timeBased()
.everyDays(1)
.create();
}
Note:
I'm unsure whether you want to change status from Active to Inactive if the row date is not today (this was not made explicit on your question). If that's the case, uncomment the corresponding line (row[0] = "Inactive") on the sample above.
Reference:
everyDays(n)

Related

Find recently changed cell data and write it into another cell. Google Sheets

I have a simple spreadsheet two columns A,B. ColumnSpreadsheet B is populated by a (SPC Micrometer) decmil input, the timestamp shows up in column A after the data is input into column B. I need to have a separate cell on the sheet that updates with the last cell change, i.e. the machinist checks a part and it updates the next cell in column B and whatever the last measurement was is reported to a single cell that updates every time a new measurement is taken. I still want a running log of all of the measurements that were taken. I will use the data in the "last measurement cell" to report out to a gage chart on a separate sheet Gage Chart.
Any help will be helpful!!
I am using an apps script for the date stamp. I was thinking that the last measurement cell could be looking for a "NOW" cell and report the cell to the right which is the measurement taken at that time, because the B column data is in the spreadsheet before the A column cell is time stamped.
I don't have programming experience, but I have tried to use a script from gethub to look at column B and find a cell change then report the changed cell data to the last measurement cell but unsurprisingly it didn't function correctly. Also posted a similar request for information to the Google Sheets reddit.
Try this:
function dupeSheets() {
const ss = SpreadsheetApp.getActive();
const dts = Utilities.formatDate(new Date(),ss.getSpreadsheetTimeZone(),"dd/MM/yyyy HH:ss");//date string for new sheet name
const shts = ss.getSheets().filter((sh => {
if(!~sh.getName().indexOf('---')) {
return true;//use sheets that don't have ---
} else {
return false;
}
})).filter(e => e).forEach(sh => {
let vs = sh.getDataRange().getDisplayValues();//get data
let ns = ss.insertSheet(`${sh.getName()} --- ${dts}` );//insert and name new sheet
ns.getRange(1,1,vs.length,vs[0].length).setValues(vs);//copy data to new sheet
});
}
You can use this to create the trigger or do it manually
function createTrigger() {
if(ScriptApp.getProjectTriggers().filter(t => t.getHandlerFunction() == "dupeSheets").length ==0 ) {
ScriptApp.newTrigger("dupeSheets").timeBased().everyDays(1).atHour(23).create();
}
}
Please note I have not actually tested this so some modifications or adjustments may be necessary.

How to create Work Shifts clock AppScript

I am learning right now scripts functionally in Google Sheet, however, can't twist my head around constructing a very simple App script.
I have the following table (Snoopi Tab)
https://docs.google.com/spreadsheets/d/1l6nYBAqB1GWoMkIOwlykhiuMpaXdWHTo7UhgZdq6hT8/edit?usp=sharing
I want it to do this simple action:
EXAMPLE: If today is not Sunday or Saturday and the date is 14.2.14 and cell BF5 is
---> go down 3 rows and paste current time "Clocking in" working-shift
When button "IN" is clicked:
If (TODAYDATE = Value in cell in row 5) & (row 3 ==!"S") both true
Set current time in (same column just row 8)
Same with "OUT" button, but this I'll try to figure by myself.
The other answer is acceptable, but is very resource intensive and have a lot of loops to do resulting to very slow execution time especially when it gets later on the year since it will loop all those dates.
Also, the run you did on the other answer did finish successfully but didn't write anything due to it missing the actual date value. This might have been caused by a timezone issue, or by only modifying the actual date while getting the raw time of the cell value.
A better alternative would be to make use of the 4th row where it contains x value when the date is equal to the current date. By using that, you wouldn't need to loop thus resulting in faster execution time and wouldn't need to convert time thus making it safer. As long as row 4 is populated on all columns (which your problem is), there should be no issue of using this script.
Script:
function WorkClock() {
var currentDate = new Date();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.getActiveSheet();
// you only need 3rd and 4th row of data
var data = sheet.getRange("E3:NE4").getValues();
// 4th row contains 'x' when today matches the column, find that index
var indexToday = data[1].indexOf('x');
// if that column's row 3 is not 'S'
if(data[0][indexToday] != 'S')
// write the time on row 8
sheet.getRange(8, indexToday + 5).setValue(Utilities.formatDate(currentDate, ss.getSpreadsheetTimeZone(), 'HH:mm'));
}
Output:
Note:
Timezone used is based on the spreadsheet's timezone which is GMT-8. Wherever the user is, it will use GMT-8, not its local time which should be helpful in some cases.
Performance difference between this and looping all dates would be vast if we are now dealing with the later months of the year (e.g. November, December)
For the OUT button, create another function by duplicating the current function. Then replace where you write the time. Instead of row 8 (Start), write it in row 10 (Finish).
function myFunction() {
var actualDate = new Date(new Date().setHours(0, 0, 0, 0)).getTime();
var sheet = SpreadsheetApp.getActiveSheet();
var data = sheet.getRange("E3:NE6").getValues();
for(var i = 0; i < data[0].length; i++) {
if (data[2][i].getTime() === actualDate) {
if (data[0][i] !== "S") {
sheet.getRange(8, (5+i)).setValue(new Date().getHours() + ":" + new Date().getMinutes());
}
break;
}
}
}

Using for and if loops in Google Apps Script

Dear programming Community,
at first I need to state, that I am not quite experienced in VBA and programming in general.
What is my problem? I have created a topic list in google sheets in order to collect topics for our monthly meeting among members in a little dance club. That list has a few columns (A: date of creation of topic; B: topic; C: Name of creator; ...). Since it is hard to force all the people to use the same format for the date (column A; some use the year, others not, ...), I decided to lock the entire column A (read-only) and put a formular there in all cells that looks in the adjacent cell in column B and sets the current date, if someone types in a new topic (=if(B2="";"";Now()). Here the problem is, that google sheets (and excel) does then always update the date, when you open the file a few days later again. I tried to overcome this problem by using a circular reference, but that doesn't work either. So now I am thinking of creating a little function (macro) that gets triggered when the file is closed.
Every cell in Column B (Topic) in the range from row 2 to 1000 (row 1 is headline) shall be checked if someone created a new topic (whether or not its empty). If it is not empty, the Date in the adjacent cell (Column A) shall be copied and reinserted just as the value (to get rid of the formular in that cell). Since it also can happen, that someone has created a topic, but a few days later decides to delete it again, in that case the formular for the date shall be inserted again. I thought to solve this with an If-Then-Else loop (If B is not empty, then copy/paste A, else insert formula in A) in a For loop (checking rows 1 - 1000). This is what I have so far, but unfortunately does not work. Could someone help me out here?
Thanks in advance and best regards,
Harry
function NeuerTest () {
var ss=SpreadsheetApp.getActive();
var s=ss.getSheetByName('Themenspeicher');
var thema = s.getCell(i,2);
var datum = s.getCell(i,1);
for (i=2;i<=100;i++) {
if(thema.isBlank){
}
else {
datum.copyTo(spreadsheet.getActiveRange(), SpreadsheetApp.CopyPasteType.PASTE_VALUES, false);
}}
}
The suggested approach is to limit the calls to the Spreadsheet API, therefore instead of getting every cell, get all the data at once.
// this gets all the data in the Sheet
const allRows = s.getDataRange().getValues()
// here we will store what is written back into the sheet
const output = []
// now go through each row
allRows.forEach( (row, ind) => {
const currentRowNumber = ind+1
// check if column b is empty
if( !row[1] || row[1]= "" ){
// it is, therefore add a row with a formula
output.push( ["=YOUR_FORMULA_HERE"] )
} else {
// keep the existing value
output.push( [row[0]] )
}
})
Basically it could be something like this:
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Themenspeicher');
var range = sheet.getRange('A2:B1000');
var data = range.getValues(); // <---- or: range.getDisplayValues();
for (let row in data) {
var formula = '=if(B' + (+row+2) + '="";"";Now())';
if (data[row][1] == '') data[row][0] = formula;
}
range.setValues(data);
}
But actual answer depends on what exactly you have, how your formula looks like, etc. It would be better if you show a sample of your sheet (a couple of screenshots would be enough) 'before the script' and 'after the script'.

Need to get permanent timestamps for a Google Sheets Dashboard

I'm trying to build a dashboard for activities in google sheets, for this i have a page where the students can input the activity name and it status:
Activities page
And i need to get the timestamp for when the activity status has been modified, witch happens in this other page:
dashboard page
Where i'am using the flag to lock the time and date that a get with the now() function.
The problems is: every time the page updates, i lose the timestamp. How can I get it permanently?
You can use Apps Script to achieve your goal:
Use the onEdit
trigger
to check when a certain cell is manually changed.
Check if the cell is from Column Status and get the Row.
Write the TimeStamp in the other Sheet depending on the value of Status
For example:
function onEdit(event){
var editedSheet = event.source.getActiveSheet();
var eRange = event.range;
// If the name is Activities and the column edited is A
if (editedSheet.getName() == "Activities" && eRange.getColumn() == 1){
var sprsheet = SpreadsheetApp.getActiveSpreadsheet();
var dashboard = sprsheet.getSheetByName("Dashboard");
var dshbValues = dashboard.getRange("B1:L1").getValues(); //Values in Dashboard
//Compare the value introduced with the Values in Dashboard
for (var i = 0; i < dshbValues[0].length; i++){
//If they coincide, write the timestamp in the row edited, correspondent column
if (eRange.getValue() == dshbValues[0][i]){
var timestamp = new Date();
dashboard.getRange(eRange.getRow(), i + 2).setValue(timestamp);
}
}
}
}
Take into account that the first time you will need to manually run the script to give the needed permissions. Also, the values in Activities Column C and Dashboard Column A need to be in the same order for coherence purposes.
References:
Range Class
Get Values
Set values
Date()

How to set timer for a row in google sheets

I would like to set a timer for an entire row in google sheets where a user can start entering data in the second row only after a certain time after starting row one.
Example: If a user starts filling cells in row 1 then they should be able to fill the data in the second only after the timer ends.
Could anyone suggest me how to get started or suggest me a chrome extension for this use?
You could also suggest me on how to build the chrome extension I can try it along with my colleagues.
This function uses an onEdit trigger to impose a 20 second delay between editing rows. It may not be exactly what you want but perhaps it's a start. It uses PropertiesService to keep state. I think user properties would be a better choice but script properties are easier to develop with since you can modify them directly in the script editor.
function onEdit(e) {
const sh=e.range.getSheet();
const delay=20000;
let ms=Number(new Date().valueOf()).toFixed();
if(sh.getName()=='Sheet10') {
const ps=PropertiesService.getScriptProperties();
let dObj=ps.getProperties();
if(dObj.hasOwnProperty('row') && dObj.hasOwnProperty('delay')) {
if(dObj.row!=e.range.rowStart && Number(ms-dObj.delay)<delay) {
e.range.setValue(e.oldValue);
e.source.toast('Sorry you have ' + (delay-Number(ms-dObj.delay))/1000 + ' seconds left.');
}else{
ps.setProperties({'row':e.range.rowStart,'delay':ms});
}
}else{
ps.setProperties({'row':e.range.rowStart,'delay':ms});
}
}
}
Issue with Protections:
Class Protection is commonly used to protect ranges from being edited. It is not appropriate for your situation, though, because, as specified here, users who are executing the script cannot remove themselves from the list of editors:
Neither the owner of the spreadsheet nor the current user can be removed.
Using oldValue:
Because of this, the best way to go would be to use the parameter oldValue from the onEdit event object.
An onEdit trigger runs every time a user edits a cell. In it, you can use:
PropertiesService to store useful information: (1) whether it is the first time row 1 is edited (isNotFirstTime), and (2) when was last time first row was edited (startTime).
Event object to get information on the edited cell (its row, its old value, etc.).
You can do something along the following lines (check comments):
function onEdit(e) {
var current = new Date(); // Current date
var range = e.range;
var editedRow = range.getRow();
var sheet = range.getSheet();
var props = PropertiesService.getScriptProperties();
var waitingTime = 20 * 1000; // 20 seconds
var isNotFirstTime = props.getProperty("isNotFirstTime"); // Check if first row was previously edited
var startTime = new Date(props.getProperty("startTime")); // Time when first row was first edited
if (editedRow === 1 && !isNotFirstTime) { // Check that (1) edited row is first one, (2) it was not edited before
props.setProperty("startTime", current.toString()); // If it's first time first row was edited, store current time
Utilities.sleep(waitingTime); // Wait for 20 seconds
props.setProperty("isNotFirstTime", true); // Store: first row was previously edited
}
// Check that (1) second row edited, (2) Less than 20 seconds passed since first time first row was edited:
if (editedRow === 2 && (current - startTime) < waitingTime) {
range.setValue(e.oldValue || ""); // Set previous value to edited cell (this avoids editing cells)
}
}
Reference:
onEdit(e)
onEdit Event object
Class PropertiesService