I'm pretty new to scripting in Sheets, and I can't get this to work. I have two columns. In column A I want the date to appear and "freeze" in the cell when I fill out the cell next to it in column B, and only then.
I have tried several things, and got inspiration from here: Freeze a date, once entered?, but I can't get it to work. I basically want:
In cell in A1: =IF(ISBLANK(B1), "", TODAY())
But today is not supposed to be updated unless I change B1.
I've tried:
function FreezeDate(String) {
if (String == "") {
return ""
} else {
return new Date()
}
}
I've also tried using OnEdit(String) with the same body as FreezeDate but this also changes the date when I open the sheet.
Is this what your looking for?
The date is only added to column one when column two of the same line is edited and column one is blank. So any further edits of column two do not change the date entry in that row.
function insertDateCol1WhenCol2IsEdited(e){//Use with an installable onEdit trigger
var sheetname='Your Sheet Name'
var rg=e.range;
var sh=rg.getSheet();
if(sh.getName()!=sheetname){return;}
if(sh.getName()==sheetname && rg.columnStart==2 && sh.getRange(rg.rowStart,1).isBlank()){
sh.getRange(rg.rowStart,1).setValue(new Date());
}
}
Thank you for response. I thought onEdit(e) had to be given an argument, and that it fired when this was changed. I know see that this is not the case. I think I've figured it out:
function onEdit(e) {
var ss = SpreadsheetApp.getActive();
var activeRow = ss.getActiveCell().getRow();
var activeCol = ss.getActiveCell().getColumn();
var activeCellValue = ss.getActiveCell().getValue();
// target cell is in same row as active cell and in column 4 (D)
var targetCell = SpreadsheetApp.getActiveSheet().getRange(activeRow, 4)
// only trigger when change is in column A, when a cell is '-' should not
trigger
if (activeCol == 1 && activeCellValue != '-') {
targetCell.setValue(new Date())
} else {
targetCell.setValue("")
}
}
Related
I'm trying to write script that will achieve the following logic:
If cell J1 has a value of 1, show columns 1-10. If cell J1 has a value of 2, show columns 1-18. If cell J1 has a value of 3, show columns 1-26. If cell J1 has a value of 4, show columns 1-36.
Here' what I have so far:
function onOpen(e) {
hideVersions_(e);
}
function hideVersions_() {
// get current sheet
var sheet = e.source.getActiveSheet();
// get row and column of edited cell
var row = e.range.getRow();
var col = e.range.getColumn();
if (col == 10 && row == 1 && sheet.getSheetName() == 'Design') {
if (e.value === "2") {
sheet.hideColumns(19, 17); // hide column 19-36 (S-AJ)
sheet.showColumns(1,17); // show column 1-18 (A-R)
}
if (e.value === "3") {
sheet.hideColumns(27, 9); // hide column 27-36 (AB-AJ)
sheet.showColumns(1,25); // show column 1-26 (A-AA)
}
if (e.value === "4")
sheet.showColumns(1,35); // show column 1-36 (A-AJ)
}
else {
sheet.hideColumns(11, 25); // hide column 11-36 (K-AJ)
sheet.showColumns(1,9); // show column 1-10 (A-J)
}
}
As I tried to get this to work I noticed you had an Underscore on your Function Name
function hideVersions_() {
That kept me from running and testing that Function.
You also Passed e from the onOpen Event into the hideVersion Function, but did not declare e as a parameter.
Instead of the onOpen Event do you want to use the onChange Event? Even though you might be making other changes we can modify the script to only look at cell J1 on a specific sheet. You'll notice in the code below I added an if statement to check if e is Null, this allowed me to test the file in the editor with out the triggering event.
I think your logic in your if statement can be simplified, not sure why you are checking if the sheet is a certain sheet of the spreadsheet. I also tend to grab the Value I want instead of letting the e event be the most recently edited cell. This is why I removed the row and col check from your first if statement.
In your if statements you "stringed" your values, but I am assuming you are putting numbers into the cell, and therefore your === is expecting numbers on both sides. Removing the quotes fixed that issue.
In your if statements you hide the new cells and then show cells, I think this can be simplified by Showing all cells and then hiding the new cells.
Below is the code that I got to work that completes what you asked for:
function onOpen(e)
{
hideVersions(e);
}
function hideVersions(e)
{
// get current sheet
if ( e == null) // When you run this Function in Editor
{
var sheet = SpreadsheetApp.getActiveSheet();
}
else
{
var sheet = e.source.getActiveSheet();
}
// get Control Cell J1
var controlValue = sheet.getRange(1,10).getValue(); // J1 is Row 1 Col 10
Logger.log(controlValue);
var lastColumn = sheet.getLastColumn();
if (sheet.getSheetName() == 'Design')
{ //since we grabed the cell explicitly, only Need to check SheetName
if (controlValue === 2)
{
Logger.log("Hiding s-AJ");
sheet.showColumns(1, lastColumn); // show All
sheet.hideColumns(19, 17); // hide column 19-36 (S-AJ)
}
if (controlValue === 3)
{
Logger.log("Hiding AB-AJ");
sheet.showColumns(1, lastColumn) //Show All
sheet.hideColumns(27, 9); // hide column 27-36 (AB-AJ)
}
if (controlValue === 4)
{
Logger.log("Showing all");
sheet.showColumns(1,lastColumn); // show all
}
else
{
Logger.log("Hiding K-AJ");
sheet.showColumns(1, lastColumn); // Show All
sheet.hideColumns(11, 25); // hide column 11-36 (K-AJ)
}
}
}
This is what the onOpen() event object looks like:
{"authMode":"LIMITED","range":{"columnEnd":1,"columnStart":1,"rowEnd":1,"rowStart":1},"user":{"email":"redacted","nickname":"redacted"},"source":{}}
Please note: no e.value
I have included a test sheet to make this easier:
https://docs.google.com/spreadsheets/d/1XnKnj0lkLmWrs1GcQRBUc_HKd-WEajlKmsX_Rlj-fqI/edit?usp=sharing
What I would like to have happen is, when I select the option "Started" from the dropdown in column D, it automatically populates column F in the same row with the current time and date. And preferably makes this uneditable. Then when the dropdown in column D is changed to "Completed", it populates another timestamp in column G, which should likewise make it uneditable.
I have tried various combinations of code, but can't find the right one. I'm not very experienced with javascript and I don't know how to write it on my own.
You can use Triggers.
Triggers let Apps Script run a function automatically when a certain
event, like editing a cell, occurs.
Try this:
function onEdit(e) {
var row = e.range.getRow();
var col = e.range.getColumn();
if(row > 1 && col == 4){
var selected = e.value;
var date = Utilities.formatDate(new Date(), "GMT", "MM/dd/yyyy-HH:mm:ss");
var sh = e.range.getSheet();
var colEdit = 0;
if(selected == "Started"){
colEdit = parseFloat(col+2);
}else{
colEdit = parseFloat(col+3);
}
var editedRange = sh.getRange(row,parseInt(colEdit),1,1);
editedRange.setValue(date);
//Optional: Uncomment the code below if you want to add protection to the cell
//This will make the cell uneditable to the other users.
//editedRange.protect();
}
}
Note: Since you are the owner of the spreadsheet, you cannot make the cell uneditable. But you can add Protection to make it uneditable to others.
Output:
Hello masters/mentors/teachers
I'm having problem with my existing project. I'd like to try here if anyone can help me
Note: Timestamp is script coded
Column A
a Dropdown Cell (Not yet started, In Progress, Completed)
Column B
if Column A on the same row choose " In Progress ", the Column B will be having a timestamp (time start)
Column C
if Column A on the same row choose " Completed ", the Column C will be having a timestamp (time end)
My problem is sometimes people may forgot to put "In Progress" first and just proceed with "Completed".
Is there a way to restrict the column C Cell if the column B is blank ? therefore they can't proceed with "Completed" if the "In Progress" timestamp are not yet available and Column C will only get timestamp when Column B's no longer blank
What you can do is setup a trigger onEdit that will check when a user select 'Completed' if there is a start date. If not the script revert back the dropdown to previous value and display a warning message.
To work you must setup the onEdit() as an installable trigger.
It will looks like :
function setupTrigger() {
var sheet = SpreadsheetApp.openById(YOUR_SHEET_ID);
ScriptApp.newTrigger("control")
.forSpreadsheet(sheet)
.onEdit()
.create();
}
function control(e) {
Logger.log(e)
var range = e.range;
var rangeCol = range.getColumn();
var rangeRow = range.getRow();
// Logger.log(rangeCol)
if(rangeCol == 1){
var dateStart = range.getSheet().getRange(rangeRow,2).getValue();
var selection = e.value;
// Logger.log(dateStart);
// Logger.log(selection);
if(selection == 'Completed' && dateStart == ""){
Logger.log('User select completed and the start date is empty');
var ui = SpreadsheetApp.getUi();
ui.alert('Please select In Progress before completed, satrt date can\'t be empty.');
if(!e.oldValue){
range.getSheet().getRange(rangeRow,rangeCol).clearContent();
}else{
range.getSheet().getRange(rangeRow,rangeCol).setValue(e.oldValue)
}
}else{
Logger.log('Do nothing')
}
}else{
Logger.log('Not good column');
}
}
Install it :
Enter the sheet id on the place of YOUR_SHEET_ID.
Run the function setupTrigger()
I setup basic control on the start date but you can check if it is a date, if the value is older than now or not etc... it depends your need but this code give you the basic to implement the control you want.
Stéphane
Using the methods from the Spreadsheet Service [1] and an installable onEdit trigger [2][3], I set up the following code that will run when someone edit your Spreadsheet, if any cell in A column is change to "Not yet started" it'll protect the column C cell in that row from being edited (for anyone besides you). If the cell in A column is change to "In Progress" it'll remove the protection created before:
function onEditFunction(e) {
var range = e.range;
var col = range.getColumn();
var row = range.getRow();
var value = e.value;
if(col == 1) {
if(value == "Not yet started") {
//Protect column C in that row
var protection = range.offset(0, 2).protect();
protection.removeEditors(protection.getEditors());
}
else if(value == "In Progress") {
var rangeC = range.offset(0, 2);
var sheet = range.getSheet();
var protections = sheet.getProtections(SpreadsheetApp.ProtectionType.RANGE);
for (var i = 0; i < protections.length; i++) {
var protection = protections[i];
if (protection.getRange().getA1Notation() == rangeC.getA1Notation()) {
protection.remove();
}
}
}
}
}
function createSpreadsheetOnEditTrigger() {
var ss = SpreadsheetApp.getActive();
ScriptApp.newTrigger('onEditFunction')
.forSpreadsheet(ss)
.onEdit()
.create();
}
You need to run the createSpreadsheetOnEditTrigger function once to create the onEdit trigger that will run with your credentials.
[1] https://developers.google.com/apps-script/reference/spreadsheet
[2] https://developers.google.com/apps-script/guides/triggers/installable
[3] https://developers.google.com/apps-script/guides/triggers/events
I have a planner type Google spreadsheet where data added daily by 8-10 users. When I add a date to a cell, I want all the cells in the same row after that date to be formatted and added a text value something like "ENDED".
At the moment I am doing it with conditional formatting and with an ArrayFormula to add the text value. The problem is that for the ArrayFormula to work the cells must be empty and in my sheet the cells they might contain data before the "ENDED" date cell.
Is there a way to do this with a script?.... and if the script can handle also the formatting of the cells that will be the best solution.
Here is my sample file to understand better what I am trying to do...
https://docs.google.com/spreadsheets/d/1QplyEcNu-svYwFq9wvPVEKnsEP1AnrlAkbBxNwEFPXg/edit#gid=2087617521
You can do this with a trigger and a custom function.
Create a new apps script project and use this code:
function onEdit(e) {
if (e.range.getColumn() ==2) {
//User edited the date column
if (typeof e.range.getValue() === typeof new Date()) {
//Value of edit was a date
endColumns(e.range.getRow(), e.range.getValue());
} else if (e.range.getValue() === "" || e.range.getValue() === null) {
var sheets = SpreadsheetApp.getActiveSheet();
var resetRange = sheets.getRange(e.range.getRow(), e.range.getColumn()+1, 1, sheets.getMaxColumns()-e.range.getColumn());
resetRange.clear(); //Will delete all text, not only the "ENDED" text.
}
}
}
function endColumns(rowNum, limitDate) {
var sheets = SpreadsheetApp.getActiveSheet();
var colOffset = 3; //Offset to account for your row Headers
var dateHeader = sheets.getRange(1, colOffset, 1, sheets.getMaxColumns()-colOffset);
var availableDates = dateHeader.getValues()[0];
var foundCol = 0;
for (var i=0; i<availableDates.length; i++) {
if (availableDates[i]>=limitDate) {
break;
}
foundCol++;
}
var rewriteCells = sheets.getRange(rowNum, foundCol+colOffset, 1, sheets.getMaxColumns()-(foundCol+colOffset));
//Add your formatting and text below:
rewriteCells.setValue("Ended");
rewriteCells.setBackground("red");
rewriteCells.setFontColor("yellow");
//Clear all cells that are "white" (no header)
for (var i=0; i<availableDates.length; i++) {
if (availableDates[i]==="" || availableDates[i] ===null) {
sheets.getRange(rowNum, colOffset+i).clear();
}
}
}
Then, create a trigger to run the onEdit function on every edit.
In this case there are some hardcoded values:
e.range.getColumn() == 2 for the row where you add the dates on
var colOffset = 3 for the number of columns to skip before reading the dates
Hope this helps!
I'll start this off by saying I have no clue what I'm doing. I'm surviving off copying and pasting code off the internet for a spreadsheet me and my friends use for watching films together.
I've run into an issue where I'm updating a cell with the current date when another cell in that row is updated if its blank with a script.
This issue is I then use a function in the cell next to it to give the difference in days for another date marked down in a cell (like a normal spreadsheet as that easier for me to do). But every time the script runs the function breaks and is replaced with the text "#NUM!" (Actually has that text as the function disappears from inside it).
I tried changing it to =U2 and that breaks also. Is this something that can't be done? The great almighty google god has not provided me with an answer so I've made an account here in hope of salvation.
tl;dr Scrips look like they are breaking my cell references for any sheet function that looks at cells they edit. How stop?
In cell V2 I have the function =DATEDIF(S2,U2,"D")
Script bellow (I know not how to format)
function onEdit(event) {
var eventRange = event.range;
var sheetName = SpreadsheetApp.getActiveSheet().getSheetName();
if (sheetName == "Scores") {
if (eventRange.getColumn() == 10) { //Check which is updated
var columnXRange = SpreadsheetApp.getActive().getSheetByName("Scores").getRange(eventRange.getRow(), 21, eventRange.getNumRows(), 21);//where to write
var values = columnXRange.getValues();
for (var i = 0; i < values.length; i++) {
if (!values[i][0]) { // If cell isn't empty
values[i][0] = new Date();
}
}
columnXRange.setValues(values);
}
}
}
Ok, I see the problem. You are looking at a way bigger range than you want with
var columnXRange = SpreadsheetApp.getActive().getSheetByName("Scores").getRange(eventRange.getRow(), 21, eventRange.getNumRows(), 21);
You only really need the value of one cell to check if it is empty. Try replacing your function with :
function onEdit(event) {
var eventRange = event.range;//makes shit happen?
var sheetName = SpreadsheetApp.getActiveSheet().getSheetName();//checks current shit
if (sheetName == "Scores") {//name of sheet want shit to happen
if (eventRange.getColumn() == 10) { // 1 is column A, 2 is B ect
// getRange(row, column, numRows, numColumns) sheet name to make not everywhere
var columnXRange = SpreadsheetApp.getActive().getSheetByName("Scores").getRange(eventRange.getRow(), 21, 1, 1);//num is where will write 1 is a ect
var values = columnXRange.getValues();//takes all shit from above to use as range
if (!values[0][0]) { // If cell isn't empty
values[0][0] = new Date();//set date to the vaules in the range
}
columnXRange.setValues(values); //use the values set above and write them in
}
}
}
..and that should fix your problem. The problem with your current script is that the script is copying the "value" of your column v cells and replacing it with just a text value. This limits the range you are grabbing to just the cell you need, eliminates the for() loop, and steps over the problem entirely.