On edit trigger that inserts timestamp dependant on checkbox value - checkbox

I'm trying to develop an inventory sheet for my company I work for in Google Sheets. In the system, I'm wanting if a checkbox in Column C is ticked it will display the "checked out time" in Column D, and if unticked will display the "checked in time" in Column E. However, I'm wanting when the item is checked in, the checked out date/ time cell turns back to blank.
I've used this custom function script to create a TIMESTAMP function that works, but every time the spreadsheet is reopened is recalculates all the date to the current date/time. Any way of making it non-volatile?
function timestamp() {
return Date();
}
Would I need a onedit trigger that could do what I described above?
WARNING: I'm not scripting savvy at all.I've been scouring the forums for the past week and can't get anything that works. Any help would be greatly appreciated :D
Link: https://docs.google.com/spreadsheets/d/1Oj6eustjvk9opXFNR2jHa0uMjPnKVXsvwOxFjb1t7-8/edit?usp=sharing

Here is the code i used for exact same scenario.
probably not the most efficient code but it works,
you can adjust it to your needs
var timezone = "GMT-0400";
var timestamp_format = "MM/dd/yyyy HH:00"; // Timestamp Format.
function onEdit() {
var s = SpreadsheetApp.getActiveSheet();
if( s.getName() == "Sheet1" ) { //checks that we're on the correct sheet
var r = s.getActiveCell(); //watches edited cell
if( r.getColumn() == 2 ) { //checks if edit is in column 2,
var nextCell = r.offset(0, 2);
if( r.getValue() === false)
nextCell.setValue(new Date()).setNumberFormat("dd-MM-yyyy HH:mm:ss");
if( r.getColumn() == 2 ) { //checks the column
var nextCell = r.offset(0, 1);
if( r.getValue() === true)
nextCell.setValue(new Date()).setNumberFormat("MM-dd-yyyy hh:mm:ss");
if( r.getValue() === false)
Browser.msgBox("Data refreshed.");
};
};
}
}

Related

How to add two actions to a script / work simultaneously

I have tried time and time again to utilize this same script & formatting to try to also get the "lost" checkbox to move to the "lost" tab (on the same script) and I've been at a loss. Any help on how to get two identical actions/scripts to work simultaneously? My current script allows the checkbox for "sold" to go in a "sold" tab, and i need the same for "lost" if that makes sense.
function onEdit(e) {
let sheet;
if (e.range.columnStart !== 9
|| e.range.rowStart === 2
|| !(sheet = e.range.getSheet()).getName().match(/^(Cynthia|Jenni|Kelsi|Monte)$/i)) {
return;
};
const targetSheet = SpreadsheetApp.getActive().getSheetByName('Sold');
const targetRange = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
sheet.getRange(e.range.rowStart, 1, 1, 8).moveTo(targetRange);
sheet.deleteRow(e.range.rowStart);
}
I think you should try changing
!(sheet = e.range.getSheet()).getName().match(/^(Cynthia|Jenni|Kelsi|Monte)$/i)
into
!(sheet === e.range.getSheet()).getName().match(/^(Cynthia|Jenni|Kelsi|Monte)$/i)

Using onEdit(e) to update cells when new value is added

A piece of code I am working on consists of two functions - generateID and onEdit(e).
generateID - generates a number using pattern and it works fine.
onEdit(e) is the one I am having issue with.
In column A of a spreadsheet, a user selects a value from a dropdown (initially column A is empty, range is from row 2 to getLastRow(), row 1 for heading).
Once the value is selected, I need the result of the generateID to be inserted into the next column B of the same row.
If a value in coumn A is then changed (selected from dropdown), I need generateID to update its value.
Here is what I tried so far:
function onEdit(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var activeSheet = ss.getSheetByName("TEMP");
var range = activeSheet.getRange(2, 1, activeSheet.getLastRow(), 1).getValues();
for (var i =0; i < range.length; i ++) {
if (e.range [i][0] !== "") {
activeSheet.getRange(i + 2, 2, 1, 1).setValue(generateID());
};
};
};
onEdit seems to be the right choice to watch changes in the range. With (e) the code seems not to work at all. Trying it as onEdit() works but incorrectly.
Apprecaite any help on this.
Thank you.
e.range returns a range object and not a array1. Try this instead of your code:
e.value !=='' ? e.range.offset(0,1).setValue(generateID()) : null

Apply Filter to Column with Numeric Values

I have a solution for filtering on this question.
This works perfectly with a column that has string values. When I try to filter a column with numeric values it will not work. I'm assuming it is because .setHiddenValues() will not accept numeric values. I could be wrong.
Let me explain my scenario:
The user inputs a value on an HTML interface, let's say 6634.
The HTML calls my function on .gs and passes the numeric value the user inputted.
google.script.run //Executes an Apps Script JS Function
.withSuccessHandler(updateStatus) //function to be called upon successfull completion of Apps Script function
.withFailureHandler(failStatus)
.withUserObject(button) //To pass the event element object
.projectSearch2(projectID); //Apps Script JS Function
return;
The function (on the linked question above) will take that value and bump it up against the values in a column deleting the value if it is found. What I am left with is an array of values that I do not want filtered.
function projectSearch2(projectID){
var ss = SpreadsheetApp.getActive();
var monthlyDetailSht = ss.getSheetByName('Data Sheet');
var monLastCN = monthlyDetailSht.getLastColumn();
var monLastRN = monthlyDetailSht.getLastRow();
var data = monthlyDetailSht.getRange(1,1,1,monLastCN).getValues();//Get 2D array of all values in row one
var data = data[0];//Get the first and only inner array
var projectIDCN = data.indexOf('Project Id') + 1;
//Pull data from columns before filtering
var projectIDData = monthlyDetailSht.getRange(2,projectIDCN,monLastRN,1).getValues();
//Reset filters if filters exist
if(monthlyDetailSht.getFilter() != null){monthlyDetailSht.getFilter().remove();}
//Start Filtering
var projectIDExclCriteria = getHiddenValueArray(projectTypeData,projectID); //get values except for
var rang = monthlyDetailSht.getDataRange();
var projectIDFilter = SpreadsheetApp.newFilterCriteria().setHiddenValues(projectIDExclCriteria).build();//Create criteria with values you do not want included.
var filter = rang.getFilter() || rang.createFilter();// getFilter already available or create a new one
if(projectID != '' && projectID != null){
filter.setColumnFilterCriteria(projectIDCN, projectIDFilter);
}
};
function getHiddenValueArray(colValueArr,visibleValueArr){
var flatUniqArr = colValueArr.map(function(e){return e[0];})
.filter(function(e,i,a){return (a.indexOf(e.toString())==i && visibleValueArr.indexOf(e.toString()) ==-1); })
return flatUniqArr;
}
That array is used in .setHiddenValues() to filter on the column.
Nothing is filtered however. This works for all columns that contain string values, just not columns with numeric values. At this point I'm lost.
Attempted Solutions:
Convert user variable to string using input = input.toString(). Did not work.
manually inserted .setHiddenValues for projectIDExclCriteria. Like this: var projectIDFilter = SpreadsheetApp.newFilterCriteria().setHiddenValues([1041,1070,1071,1072]).build(); That succeeded so I know the issue is before that.
Step before was calling getHiddenValueArray. I manually inserted like so: var projectIDExclCriteria = getHiddenValueArray(projectIDData,[6634]); It is not working. Is the issue with that getHiddenValueArray function not handling the numbers properly?
Here is a solution. Changing the following:
.filter(function(e,i,a){return (a.indexOf(e.toString())==i && visibleValueArr.indexOf(e.toString()) ==-1); })
To:
.filter(function(e,i,a){return (a.indexOf(e) == i && visibleValueArr.indexOf(e) == -1); })
That works! Thank you Tanaike. The next question is will this impact columns that are not numeric. I have tested that and it works as well.
How about this modification?
From :
.filter(function(e,i,a){return (a.indexOf(e.toString())==i && visibleValueArr.indexOf(e.toString()) ==-1); })
To :
.filter(function(e,i,a){return (a.indexOf(e) == i && visibleValueArr.indexOf(e) == -1); })
Note :
In this modification, the number and string can compared using each value.
If you want to use return (a.indexOf(e.toString())==i && visibleValueArr.indexOf(e.toString()) ==-1), you can achieve it by modifying from colValueArr.map(function(e){return e[0];}) to colValueArr.map(function(e){return e[0].toString();}).
In this modification, colValueArr.map(function(e){return e[0].toString();}) converts the number to string, so the number is used as a string.
Reference :
Array.prototype.indexOf()

Send email using google apps script with attachment and mark checkbox as done

I am trying to build a script which will be sending emails based on the data in a spreadsheet in google sheets.
Column 1 - emailAddress
Column 2 - subject
Column 3 - body
Column 4 - signature
Column 5 - fileName (name of the file I'd like to attach, will be different in each case)
Column 6 - checkbox (new functionality in G Sheets, marked= TRUE, unmarked=FALSE).
The aim is to send email only to those which are not marked as sent. After the script is done checkbox should change to TRUE to avoid duplications.
I wrote the below script however there are two issues:
How to make a fileName variable value which will be taken to code from column 5?
How to force the program to change the status of the checkbox to TRUE and to omit those which are already TRUE?
The function:
function Email() {
var rng = SpreadsheetApp.getActiveSheet().getActiveRange()
var sheet = SpreadsheetApp.getActiveSheet();
var data = rng.getValues();
for (i in data)
{
var rowData = data[i];
var checkbox = data[5]
var file = DriveApp.getFilesByName(data[4])
var emailAddress = rowData[0];
var body = rowData[2];
var subject = rowData[1];
var signature = rowData[3];
var message = body + '\n\n'+ signature;
if(checkbox is != 'TRUE')
{
GmailApp.sendEmail(emailAddress,subject,message,
{
attachments: [file.next().getAs(MimeType.PDF)]
}
);
cell.setValue("TRUE");
}
}
}
How to make a fileName variable value which will be taken to code from column 5?
Create a variable var fileName. For uniqueness purposes just add a counter at the end 1,2, etc so it ends looking like fileName1, fileName2, etc
How to force the program to change the status of the checkbox to TRUE and to omit those which are already TRUE?
Use a double for loops because working with sheets is like working with 2D arrays. Inside that create an if statement:
if( getValue() is != 'TRUE'){
cell.setValue("TRUE")
}
Hopefully you got the idea from the pseudocode.

Improve function delete rows by contain value

I have working code, that delete row from sheet if column corresponds to one of the conditions. It based on arrays and because of it works much more faster than standard google sheet deleteRow function. I call it like this:
deleteRowsBV('SIZ',4,'0','')
where
deleteRowsBV(listName,ColNum,FirstSearch,SecondSearch)
What I want is a call function with more or less and equal signs, like this:
deleteRowsBV('SIZ',4,<='0',=='')
But in case of my main function, it doesn't work, when I specify a variable instead of a sign and a value.
Here is main function:
function deleteRowsBV(listName,ColNum,FirstSearch,SecondSearch) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName(listName);
var DataLengBefore = sheet.getLastRow();
var DataColLeng = sheet.getLastColumn();
var data = sheet.getRange(1,1,DataLengBefore,DataColLeng).getValues();
for (var i = data.length-1; i >= 0; i--) {
if (data[i][ColNum] <= FirstSearch||data[i][ColNum] == SecondSearch) {
data.splice(i, 1);
}
}
sheet.getRange(10, 1, DataLengBefore,DataColLeng).clearContent();
sheet.getRange(10, 1,data.length,5).setValues(data);
}
Based from this related post, spreadsheet rows and columns are numbered starting at 1, for all methods in the SpreadsheetApp, while javascript arrays start numbering from 0. You need to adjust between those numeric bases when working with both. When deleting rows, the size of the spreadsheet dataRange will get smaller; you did take that into account, but because you're looping up to the maximum size, the code is complicated by this requirement. You can simplify things by looping down from the maximum.
You may refer with this thread. However, this only looks at the value from a single cell edit now and not the values in the whole sheet.
function onEdit(e) {
//Logger.log(JSON.stringify(e));
//{"source":{},"range":{"rowStart":1,"rowEnd":1,"columnEnd":1,"columnStart":1},"value":"1","user":{"email":"","nickname":""},"authMode":{}}
try {
var ss = e.source; // Just pull the spreadsheet object from the one already being passed to onEdit
var s = ss.getActiveSheet();
// Conditions are by sheet and a single cell in a certain column
if (s.getName() == 'Sheet1' && // change to your own
e.range.columnStart == 3 && e.range.columnEnd == 3 && // only look at edits happening in col C which is 3
e.range.rowStart == e.range.rowEnd ) { // only look at single row edits which will equal a single cell
checkCellValue(e);
}
} catch (error) { Logger.log(error); }
};
function checkCellValue(e) {
if ( !e.value || e.value == 0) { // Delete if value is zero or empty
e.source.getActiveSheet().deleteRow(e.range.rowStart);
}
}

Resources