This has a lot of what I assume is redundant code. I While I am making my way to understanding more of how this works, I am a beginner at crafting these from piecemealing what I have read through to have this semi workable document here:
https://docs.google.com/spreadsheets/d/1FqZNZX3zGRscG6teizjEFjmJMLKQmMLRCi6BmUL2T34/edit?usp=sharing
the Master sheet combines the other sheets into itself and automatically updates based off of the entries in the other tabs.
Question 1:
var s = SpreadsheetApp.getActiveSheet();
var r = s.getActiveCell();
var nextCell = r.offset(0,-1 );
//Movement SECTION
if( s.getName() == "Movement" )
{
if( r.getColumn() == 2 )
{nextCell.setValue("Spacing");}
}
//Defense SECTION
if( s.getName() == "Defensive" )
{
if( r.getColumn() == 2 ) {
nextCell.setValue("Defense");}
}
//Offense SECTION
if( s.getName() == "Offensive" )
{
if( r.getColumn() == 2 )
{nextCell.setValue("Offense");}
}
//erases category if the exercise is blank
if(s.getName() == "Movement" | s.getName() == "Defensive" | s.getName() == "Offensive")
{
if( r.getColumn() == 2 )
{if (r.isBlank() | r == "")
{nextCell.clearContent()
}
}
}
Is there a cleaner way to combine all of these sections? the purpose is that on edit, the first column will reflect a category depending on the tab. I currently plan to have Column A locked and hidden, so it's less of an eyesore in the actual tab.
(Bonus Question: is there a way to automatically add the category to the query on the master sheet? [the cell is B4])
Question 2:
Is it faster to use protected ranges, or would it be better to implement a script to prevent changes to certain ranges and push a window to explain what to do?
I am going to go after your 'bonus points' question below, but first:
I use JSON in some of my functions like this to both simplify the logic and make it more readable. Also, I switched the logic so it checks for a blank cell first so it doesn't unnecessarily perform a write before it deletes the content
function onEdit(e){
//checkAll(e)
var s = SpreadsheetApp.getActiveSheet();
var r = s.getActiveCell();
var nextCell = r.offset(0,-1 );
var sheetname = s.getName();
var column = r.getColumn();
var check =
{
Movement: "Spacing",
Defensive: "Defense",
Offensive: "Offense" //if you add more sheets just add them here
}
if(r.isBlank() && check[sheetname] && column == 2) { // !r.getValue() is not necessary, I believe.
nextCell.clearContent()
} else if(check[sheetname] && column == 2) {
nextCell.setValue(check[sheetname])
}
}
Next, before the above will work you are going to have to get into your script and edit/isolate your onEdit(e) function. At the moment it wraps around another function and then everything in the 'Location.gs' file is not wrapped inside a function at all and was throwing errors. I didn't try to error check that part or figure out what it was doing.
Now, lastly, going after your 'for bonus points' question, a way that you can accomplish this that only uses google sheets, some duct tape and super glue, and no google scripts:
EDIT:
Okay, so I played around with an even easier way that doesn't involve any of the duct tape & super glue. Simply replace the formula in your query with the following nested query:
=query({{query(Movement!B3:C,"select 'Space',B,C where B != ''")};{query(Defensive!B3:C,"select 'Defensive',B,C where B !=''")};{query(Offensive!B3:C,"select 'Offensive',B,C where B !=''")}},"select * where Col2 !=''")
This is short, simple, and doesn't involve having any hidden sheets at all and I think it's relatively easy to follow. Performance wise this should be pretty zippy too.
THIS ANSWER BELOW WORKS (but isn't nearly as elegant)
Create a new sheet in this workbook and in column A from row 1 to row 100 (or however many rows you think you might have as a maximum), put "Offensive" in every row., in column B put "Defensive" in every row, and in column C, put "Space" in every row. I've got an example here: copied example sheet in the 'Labels' sheet.
Next, in the cell where you have your query, put the following (for reference, the above sheet I called 'Labels':
=query({{array_constrain(Labels!A1:A100,counta(Movement!B3:B),1),filter(Movement!B3:C,Movement!B3:B<>"")};{array_constrain(Labels!B1:B100,counta(Offensive!B3:B),1),filter(Offensive!B3:C,Offensive!B3:B<>"")};{array_constrain(Labels!C1:C100,counta(Defensive!B3:B),1),filter(Defensive!B3:C,Defensive!B3:B<>"")}},"select *")
This should work for what you want to do without having to have a column for the sheet name. The example in the sheet referenced above is a working example.
I think this is it:
function onEdit(e) {
const sh=e.range.getSheet();
const shts=["Movement","Defensive","Offensive"];
const vals=["Spacing","Defense","Offense"];
const idx=shts.indexOf(sh.getName());
if(idx!=-1 && e.range.columnStart==2 && e.range.getValue()!='') e.range.offset(0,-1).setValue(vals[idx]);
if(idx!=-1 && e.range.columnStart==2 && e.range.getValue()=='') e.range.offset(0,-1).setValue('');
}
Related
I am trying to create a script in Google Sheets that will allow me to have a datestamp entered in a cell when a checkbox is checked in the row that cell is in. I have gotten the first part done thanks to web searches, but the second part of the task I am having issues with.
Once the checkbox has been checked the datestamp does indeed go in the cell, but when I uncheck the checkbox it just updates the date in the cell, but does not remove it.
I am sure I am just missing something easy, but as I do not normally code, and only have a somewhat firm grasp on formulas even, this is a bit out of my ability to troubleshoot. If anyone could look at this and help me figure out what variables would be tied to what I am doing, and which ones I have gotten wrong (ideally with comments so I can understand what each line of code is trying to do), I would appreciate it.
Here is the script:
function onEdit() {
var s = SpreadsheetApp.getActiveSheet();
if( s.getName() == "Writing Team Tasks" ) { //checks that we're on the correct sheet
var r = s.getActiveCell();
if( r.getColumn() == 5 ) { //checks that the cell being edited is in column A
var nextCell = r.offset(0, 4); { //offset for the non-adjacent column
if( r.getValue() === "") { //checks if the cell being edited is empty or not?
nextCell.setValue("");
}
else
{
nextCell.setValue(new Date());
}
}
}
}
}
I am hoping to have a script that will enter a date stamp when a checkbox is checked, and then remove the datestamp if the checkbox becomes unchecked. Currently, I am able to do all of that, except remove the datestamp.
Try this:
function onEdit(e) {
const sh = e.range.getSheet();
if (sh.getName() == "Writing Team Tasks" && e.range.columnStart == 5 && e.value =="TRUE") {
e.range.offset(0,1).setValue(new Date());
}
if (sh.getName() == "Writing Team Tasks" && e.range.columnStart == 5 && e.value =="FALSE") {
e.range.offset(0,1).setValue('');
}
}
I would prefer to use a formula first if you do not have to do it as a script.
A formula is visible on the cell and not hidden from view of someone using the worksheet.
For this, a formula would look like the Date column (B) below:
row \ col
A
B
1
Done
Date
2
☑
=IF(($A2=TRUE), TODAY(), "")
3
☐
=IF(($A3=TRUE), TODAY(), "")
And the output would look like this:
Basically, it checks if the the value of the cell is TRUE and displays today's date if so. If not, it displays a blank (empty) string
Lets say I selected Funded in drop down list located in C2 than I would like the value of B2 copied to A2
There are numbers in the column UPCOMING INCOME and drop down list in the next column. When we choose in drop down Payed (PENDING, PAYED, PAUSED) it should copy the number from the Upcoming income column and paste to column FUNDED. enter image description here
Moving F to A
function onEdit(e) {
const sh = e.range.getSheet();
if(sh.getName() == "Your Sheet Name" && e.range.columnStart == 8 && e.range.rowStart > 1 ) {
e.range.offset(0,-7).setValue(e.range.offset(0,-2).getValue());
}
}
creating markdown tables
google-apps-script reference
javascript reference
Learn More
This should theoretically do what you're asking it to do, you just need to set a trigger for 'on edit' in the triggers tab when setting up your script.
function someFunction(){
//Gets the spreadsheet with the name you specified
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet4')
//Creates a 2D-Array of values based on the range you specify
var dataRange = ss.getRange(2,6,ss.getLastRow()-1,3).getValues()
//Runs through the 2D-array, checking every value for a match
for(var i = 0; i < dataRange.length; i ++){
for(var j = 0; j < dataRange[i].length; j++){
//If there is a match, pastes the value from the 'Upcoming Income' column to the 'Funded' column
dataRange[i][j] == 'Payed' ? ss.getRange('A'+(i+2)).setValue(dataRange[i][0]) : null
}
}
}
This is at least how I would solve it, but I'm sure there are many other quicker more elegant ways of doing so. You will have to make adjustment so that it will work with your spreadsheet, but I believe they should be fairly simple. If there is an error, please let me know, and I'll do my best to fix it.
In the future, please do what #Cooper recommended and make sure your question is more clear in what it is asking, and please attempt to code it yourself first before asking for help from others.
I have an excel formula that is very simple and it works because I can restrict the recursive iterations. I am not very script savvy, but this is what it is and it works.
=IF(D24="P",IF(E24="",DateStamp,E24),IF(D24="F",IF(E24="",DateStamp,E24),""))
Its a pass/fail testing sheet and it adds a timestamp when someone passes or fails the test. We've added a few more people and I want to move the document to google apps to allow more than 1 person to work on it at the same time.
The only issue i've come in is the circular reference that this causes. In excel I can limit the # of iterations in the options, I dont have this ability anymore. Any help would be great.
EDIT: What I've tried.
I've tried to find a way to input a VBA Script that a coworker created that would work for me. I'm not good with scripting so I'm unable to make this into a google apps script:
VBA SCRIPT:
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Column = 4 Then
If Cells(Target.Row, 5).Value = "" Then
Cells(Target.Row, 5).Value = Now
End If
Else
End If
End Sub
In theory I tried to create a script that will copy a cell that has a timestamp on it and then try to 'paste special' and just paste the value into the cell needed. This would work except I was unable to find a way to paste special with the google apps scripting.
Thanks for any help
/edit
Stackoverflow is a place to ask questions related to programming, e.g. that you're actually working on. Not really asking for others to develop it for you, i.e. you didn't even started trying any Apps Script code yet. I recommend you reading its tutorials and guides. It's really easy to start.
Anyway, just to help you get started, I'll drop everything you said and stick to the question title: "automatic timestamp when a cell is filled out"
I advise you to do it all on apps script, and drop your formulas entirely, e.g.
function onEdit() {
var s = SpreadsheetApp.getActiveSheet();
if( s.getName() == "Sheet1" ) { //checks that we're on the correct sheet
var r = s.getActiveCell();
if( r.getColumn() == 4 ) { //checks the column
var nextCell = r.offset(0, 1);
if( nextCell.getValue() === '' ) //is empty?
nextCell.setValue(new Date());
}
}
}
This code does what I understood from yours, which is: if something is edited on column D and column E is empty, add the current date to E.
Just addition to above code FOR Multi Column AutoStamp in Same Sheet
function onEdit() {
var s = SpreadsheetApp.getActiveSheet();
if( s.getName() == "Sheet1" ) { //checks that we're on the correct sheet
var r = s.getActiveCell();
if( r.getColumn() == 5 ) { //checks the column
var nextCell = r.offset(0, 1);
//if( nextCell.getValue() !== '' ) //is empty?
nextCell.setValue(new Date());
}
if( r.getColumn() == 7 ) { //checks the column
var nextCell = r.offset(0, 1);
//if( nextCell.getValue() !== '' ) //is empty?
nextCell.setValue(new Date());
}
if( r.getColumn() == 9 ) { //checks the column
var nextCell = r.offset(0, 1);
//if( nextCell.getValue() !== '' ) //is empty?
nextCell.setValue(new Date());
}
}
}
You just need to use Apps Script.
I'll explain using an example:
function onEdit(e) {
var row = e.range.getRow();
if (row > 1 && e.source.getActiveSheet().getName() === "Sheet1") {
e.source.getActiveSheet().getRange(row, 14).setValue(new Date());
} else {
if ((row > 1 && e.source.getActiveSheet().getName() === "Sheet2") || (row > 1 && e.source.getActiveSheet().getName() === "Sheet3")) {
e.source.getActiveSheet().getRange(row, 6).setValue(new Date());
}}}
This first of all check which sheet is being edited. If sheet1, then it takes the 14th column of all rows (getRange(row,14)) in that sheet & whenever anything is edited (edit(e)), it adds timestamp (setValue(new Date())) in that 14th column. Similarly, if it's a different sheet,i.e., Sheet2 or Sheet3 or any other name, we can select different columns for timestamp as per requirement.
Also, if( (row > 1) condition has been added so that timestamp does NOT get added in the first row upon edit as it's usually headings.
Now you select to get a range of cells & use them as per requirement. See this question for a better idea on getRange().
and if you want it to update if the cell is changed again just delete this line
if( nextCell.getValue() !== '' ) //is empty?
By the way, how can the date be formatted to ie. dd/mm/yyyy instead of the default dd/mm/yyyy hh:MM:ss format
Actually, in this case you don't have to script anything. Google (or someone) has done it already. In your Google spreadsheet, go to "Insert -> Script" and search on "time". There are two ready-made scripts which will do what you want. I found "Cell Last Modified Date" works perfectly. Select it and click the "Install" button. You can reformat the column to show date, date+time, and so on. You can also hand code a date in the column, or move them from another column if you were tracking it before, and they will stay as you set them. But updating any cell in the row will update the timestamp.
I set the timestamp to include HH:MM:SS but upon testing the stamp 4 times in under a minute I get: 03,14,11,07 fluctuate as the MM in my timestamp.
it's much easier than that!
=now
or;
=today
Depending what you need
The project I am working on is to calculate costs of remaining sets in a mobile game. It has a spreadsheet with a list of all the sets, and checkboxes for all 5 pieces, Columns B:F. I want to include the option to hide all sets that are completed, so all Checkboxes are checked. This is done via another Checkbox, H16.
I have modified the Checkbox values to use Yes and No.
I have never used Google Apps Script before, and am very new to coding in general. I think what I need is, to use onEdit, then every time a cell is edited, check if H16 is TRUE, then scan through each row to check the B:F values. If all are true, hide that row. I don't know the best way to type that out, though.
Bonus points, I also want to include a reset checkbox, so when checked, set all values in B:F to false, and show the rows.
Here is a link to the spreadsheet
EDIT: My current GAS code, which isn't much because I don't know what I am doing:
function onEdit(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var maxSheet = 100;
if(H16 == true)
{
for(i, i<=maxSheet, i = i + 1) {
}
} else {
sheet.showRows(1, maxSheet);
}
}
Hiding rows when all five columns are true
This may not be exactly what you wish but I think it's close. I did not use yes and no values because it's easier for me to leave it true false but you can change that. I'm using Sheet0 and you can change that as well. I used less rows so you can also change that. But the basic idea is that when H16 is clicked it hides rows that have all five columns checked.
Code:
function onEdit(e) {
e.source.toast('entry');//debug
const sh = e.range.getSheet();
const sr = 2;//data start row
const lr = 15;//last row of data
sh.getRange('K1').setValue(JSON.stringify(e));//debug
if(sh.getName() == "Sheet0" && e.range.columnStart == 8 && e.range.rowStart == 16 & e.value == "TRUE" ) {
e.source.toast('past if');//debug
e.range.setValue("FALSE");
let vs = sh.getRange(sr,2,lr - sr + 1, 5).getValues();
vs.forEach((r,i) => {
if(r[0] && r[1] && r[2] && r[3] && r[4]) {
e.source.toast(`Row: ${sr + i}`);//debug
sh.hideRows(sr + i);
}
});
}
}
Image of Sheet0:
I use K1 to provide me with the event object while I debug the script. And I also use the e.source.toast in several location to get an idea of what is going on.
Animation:
an incomplete description of the event object
You can get a better understanding of the event object by using the JSON.stringify code as show in my example.
Most new people want to run the code from the script editor so I'll tell upfront that unless you provide the event object to populate the e then it's not going to work. Just copy and past it and get you unique stuff set like sheet name and data space and then proceed to edit the page and figure out how it works.
WHAT I HAVE One google spreadsheet named "Script Test" with two sheets named "delete" and "non delete".
WHAT I NEED If a row in Col B on "non delete" is changed to 'DELETE' via the drop down menu, the row with the same Buy Number on "delete" will be deleted.
WHAT I HAVE TRIED
What has worked = By researching on stack I found an onEdit function that deletes a row on "delete" based on if a cell has a specific value. In this case, that value is 'DELETE'. The problem with this is, I can only get it to work if that cell is on the sheet "delete" rather than the sheet "non delete". If I'm working off of "non delete" and need to go back to "delete" to delete a row of information, I can just right click on the row number and manually delete it. So, this script isn't necessarily saving me time.
This script is as follows:
function onEdit(e) {
try {
var ss = e.source;
var s = ss.getActiveSheet();
if (s.getName() == 'delete' &&
e.range.columnStart == 1 && e.range.columnEnd == 1 && // only look at edits happening in col A which is 1
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 == 'DELETE') {
e.source.getActiveSheet().deleteRow(e.range.rowStart);
}
}
What has not worked = I fiddled with the script a bit to have it read Col F on "delete" and in Col F I have an Index Match of Col B in "non delete". However, this does not delete the row on "delete" when Col F changes to 'DELETE'. Now I'm not 100% on this but I can pretty much deduce that this is happening because Col F isn't being "edited", rather the formula inside of it is "updating". I've also tried fiddling with other scripts that I found on stack but none seem to have gotten me as close as the script above.
THINGS TO THINK ABOUT
First of all, thanks for any help you guys can give me. Just before posting this question, I came across a filter function that I think may be a direction to head in if I'm right about the Index Match. I found one function that hides rows based on a filter but I would need the row to be deleted so I'm assuming that is as simple as switching hideRows with deleteRows. I've tried adding screenshots of what I need done but I don't have enough reputation. I can and will add a link to a copy of the spreadsheet if that helps. Once again, thanks for any tips or guidance.
Copy of Script Test
Use the getSheetByName() method to get the delete sheet.
function checkCellValue(argRowToDelete) {
if (e.value == 'DELETE') {
var toDeltSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("delete");
toDeltSheet.deleteRow(argRowToDelete);
}
}
If you want a separate function just for deleting the row, maybe do the check for the 'DELETE' text in the main function, and pass the row number to the delete function:
I've tested the following code, and it works. When the drop down list is used to select 'DELETE' in the 'non delete' sheet, it deletes the corresponding row in the 'delete' sheet.
I made multiple changes to the code. Even though this code deletes a row in a sheet different from where the edit is taking place, there is still a potential problem. Once a row in the 'delete' sheet is deleted, the rows will shift. If you start deleting rows at the top or middle, then every row below the deleted row is no longer synced with the rows in the 'delete' sheet.
So, this answers your question, but now you have yet another problem.
function onEdit(e) {
try {
var ss = e.source;
var s = ss.getActiveSheet();
var colStart = e.range.columnStart;
var colEnd = e.range.columnEnd;
Logger.log('colStart: ' + colStart);
Logger.log('colEnd: ' + colEnd);
var thisRow = e.range.getRow();
Logger.log('s: ' + s.getName());
//Avoid looking at multi column edits. If column start and column end is same column,
//then not a multi column edit
var editedFromNonDelete = (s.getName() === 'non delete');
Logger.log('editedFromNonDelete: ' + editedFromNonDelete);
var editedFromColB = (colEnd === 2) && (colStart === colEnd);
// only look at edits happening in col B
if (editedFromNonDelete && editedFromColB) {
Logger.log('e.value: ' + e.value);
if (e.value === 'DELETE') {
fncDeleteRow(thisRow);
};
}
} catch (error) {
Logger.log(error);
}
};
function fncDeleteRow(argRowToDelete) {
var toDeltSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("delete");
toDeltSheet.deleteRow(argRowToDelete);
};
After testing out the filter function for a couple minutes, I've pretty much got it to do what I needed. Thanks anyways!