I've was wondering if what to do is possible and how to go about it. This is For Google Sheets. Here is the script I'm using (Found this script and edited it for my use):
function onEdit(e) {
var s = e.source.getActiveSheet().getName();
var cols = [5];
if (s !== 'Michele' && s !== 'Janet' && s !== 'Stephanie' || cols.indexOf(e.range.columnStart) ==-1 || !e.value) return;
e.range.offset(0,1).setValue(Utilities.formatDate(new Date(), "CST", "MM/dd/yyyy HH:mm:ss"));
}
Column 5 (E) has the option to choose different statuses. So far, I have it putting the TimeStamp of Column 5's change in its adjacent column, 6 (F). What I'm trying to figure out is if the status is changed again (Pending -> Confirmed-> Installed-> ect) , instead of updating Column 6, which would already be TimeStamped, it will instead move on to Column 7, then 8, then 9, ect.
Let me know if more detail is needed.
Thanks a Bunch!
I've tested this code and it works. It finds the number of cells to the right of column E that already have values in them, and then calculates what the next empty column is, and writes the new date to the next empty column.
function onEdit(e) {
var sh = e.source.getActiveSheet();
var s = sh.getName();
var lastCol = sh.getLastColumn();
var cols = [5];
var startCol = SpreadsheetApp.getActiveSpreadsheet().getRangeByName('ColumnE').getColumn();
//In the spreadsheet, name a range that is in the column you want to start at
if (s !== 'Michele' && s !== 'Janet' && s !== 'Stephanie' || cols.indexOf(e.range.columnStart) ==-1 || !e.value) return;
var rowEdited = e.range.getRow(); //Get the row that was edited
//Start in row edited and column 6, get 1 row and X columns
var datesInCells = sh.getRange(rowEdited,startCol,1,lastCol - 5).getValues();
Logger.log('datesInCells: ' + datesInCells);
Logger.log('datesInCells.length: ' + datesInCells[0].length);
var numberOfDates = 0;
for (var i=0;i<datesInCells[0].length;i+=1) {
if (datesInCells[0][i] !== "") {
numberOfDates +=1;
};
};
Logger.log('numberOfDates: ' + numberOfDates);
var colToWriteTo = startCol + numberOfDates;
var newDate = Utilities.formatDate(new Date(), "CST", "MM/dd/yyyy HH:mm:ss");
//var theStatus = sh.getRange(rowEdited, 5).getValue();
var theStatus = e.value;
var bothStatusAndDate = newDate + " : " + theStatus;
sh.getRange(rowEdited,colToWriteTo).setValue(bothStatusAndDate);
};
Version two:
function onEdit(e) {
var sh = e.source.getActiveSheet();
var s = sh.getName();
var lastCol = sh.getLastColumn();
var cols = [5];
if (s !== 'Michele' && s !== 'Janet' && s !== 'Stephanie' || cols.indexOf(e.range.columnStart) ==-1 || !e.value) return;
var rowEdited = e.range.getRow(); //Get the row that was edited
//Start in row edited and column 6, get 1 row and X columns
var datesInCells = sh.getRange(rowEdited,6,1,lastCol - 5).getValues();
Logger.log('datesInCells: ' + datesInCells);
Logger.log('datesInCells.length: ' + datesInCells[0].length);
var numberOfDates = 0;
for (var i=0;i<datesInCells[0].length;i+=1) {
if (datesInCells[0][i] !== "") {
numberOfDates +=1;
};
};
Logger.log('numberOfDates: ' + numberOfDates);
var newDate = Utilities.formatDate(new Date(), "CST", "MM/dd/yyyy HH:mm:ss");
if (numberOfDates > 4) {
var existingValues = sh.getRange(rowEdited,7,1,4).getValues();
sh.getRange(rowEdited,6,1,4).setValues(existingValues);
sh.getRange(rowEdited,10).setValue(newDate);
return;
};
var colToWriteTo = 6 + numberOfDates;
sh.getRange(rowEdited,colToWriteTo).setValue(newDate);
};
Related
I have 1 spreadsheet with multiple sheets.
The 1st and the 2nd sheets sometimes have similar data in the rows (duplicates). Also, each sheet has a column (8) with a checkbox.
Task: I need to move one of the duplicates to the 3rd sheet when the checkboxes in both sheets (1st and 2nd) are checked.
Here is the code that moves the row when it's checked in a single sheet.
Can someone help me modify it to complete my task?
function onEdit(e) {
let range = e.range;
let col = range.getColumn();
let row = range.getRow();
let val = range.getValue();
let source = e.source.getActiveSheet();
let ss = SpreadsheetApp.getActiveSpreadsheet();
let sourceSheet = ss.getSheetByName(source.getName());
if (sourceSheet.getName() == 'SHEET1' && col == 8 && val == true){
let data = sourceSheet.getRange(row,1,1,7).getValues();
let targetSheet = ss.getSheetByName('SHEET2');
targetSheet.appendRow(data[0]);
sourceSheet.deleteRow(row);
}
if (sourceSheet.getName() == 'SHEET2' && col == 8 && val == true){
let data = sourceSheet.getRange(row,1,1,7).getValues();
let targetSheet = ss.getSheetByName('SHEET3');
targetSheet.appendRow(data[0]);
sourceSheet.deleteRow(row);
}
}
Sample Spreadsheet
Apply filter()
You can add the filter() method (combined with the JSON.stringify() method) to your script to determine if a row is a duplicate of another from the other sheet. The modified script should somehow look like this:
function onEdit(e) {
var range = e.range;
var col = range.getColumn();
var row = range.getRow();
var val = range.getValue();
var source = e.source.getActiveSheet();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sourceSheet = ss.getSheetByName(source.getName());
var targetSheet = ss.getSheetByName('Sheet3');
//If Sheet1 checkbox is triggered
if (sourceSheet.getName() == 'Sheet1' && col == 8 && val == true) {
var rowArr = ss.getSheetByName('Sheet1').getRange(row, 1, 1, 8).getValues();
var lastRow2 = ss.getSheetByName('Sheet2').getLastRow();
var sheet2Values = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet2').getRange(1, 1, lastRow2, 8).getValues();
var count = sheet2Values.filter(x => {
return (JSON.stringify(x) === JSON.stringify(rowArr[0]))
});
var otherRow = sheet2Values.map((x, i) => {
if (JSON.stringify(x) === JSON.stringify(rowArr[0])) {
return i;
}
});
if (count.length > 0) {
var otherRow = sheet2Values.map((x, i) => {
if (JSON.stringify(x) === JSON.stringify(rowArr[0])) {
return i;
}
}).filter(y => y);
rowArr[0].pop();
targetSheet.appendRow(rowArr[0]);
sourceSheet.deleteRow(row);
var otherSheet = ss.getSheetByName('Sheet2');
otherSheet.deleteRow(otherRow[0] + 1)
}
}
//If Sheet2 checkbox is triggered
if (sourceSheet.getName() == 'Sheet2' && col == 8 && val == true) {
var rowArr = ss.getSheetByName('Sheet2').getRange(row, 1, 1, 8).getValues();
var lastRow = ss.getSheetByName('Sheet1').getLastRow();
var sheet1Values = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1').getRange(1, 1, lastRow, 8).getValues();
var count = sheet1Values.filter(x => {
return (JSON.stringify(x) === JSON.stringify(rowArr[0]))
});
if (count.length > 0) {
var otherRow = sheet1Values.map((x, i) => {
if (JSON.stringify(x) === JSON.stringify(rowArr[0])) {
return i;
}
}).filter(y => y);
rowArr[0].pop();
targetSheet.appendRow(rowArr[0]);
sourceSheet.deleteRow(row);
var otherSheet = ss.getSheetByName('Sheet1');
otherSheet.deleteRow(otherRow[0] + 1)
}
}
}
Output
Based on your sample spreadsheet, the modified script should work as shown below:
Please take note that the appendRow() method appends any row to the last non-empty row in the sheet. A row with a checkbox is not considered to be an empty row thus explains the behavior of the output.
Reference
filter()
JSON.stringify()
Would that do what you want ?
function copyDuplicateRows() {
var spreadsheet = SpreadsheetApp.getActive();
var sheet1 = spreadsheet.getSheetByName("Sheet1");
var sheet2 = spreadsheet.getSheetByName("Sheet2");
var sheet3 = spreadsheet.getSheetByName("Sheet3"); // destination sheet
var data1 = sheet1.getDataRange().getValues();
var data2 = sheet2.getDataRange().getValues();
// Use a JavaScript object to keep track of duplicate rows
var seen = {};
// Iterate through data1 and copy unique rows to sheet3
for (var i = 0; i < data1.length; i++) {
var row = data1[i];
var key = row.join(); // create a unique key for each row
if (!seen[key]) {
seen[key] = true;
sheet3.appendRow(row);
}
}
// Iterate through data2 and copy unique rows to sheet3
for (var i = 0; i < data2.length; i++) {
var row = data2[i];
var key = row.join();
if (!seen[key]) {
seen[key] = true;
sheet3.appendRow(row);
}
}
}
Working in AppScript with Google Sheets and I have this script working to add notes based off of a cell value in a different column (selectedCell3 portion of the script). I'm wanting to add logic that essentially ADDS new text to the notes IF the cell value is changed. This is as far as I've gotten...
Still learning... be kind :)
//* Creates a Date Stamp if a column is edited.
//*/
//CORE VARIABLES
// The column you want to check if something is entered.
var COLUMNTOCHECK = 22;
// The SECOND column you want to check if something is entered.
var COLUMNTOCHECK2 = 18;
// The THIRD column you want to check if something is entered.
var COLUMNTOCHECK3 = 10;
// Where you want the date time stamp offset from the input location. [row, column]
var DATETIMELOCATION = [0,1];
// Where you want the SECOND date time stamp offset from the input location. [row, column]
var DATETIMELOCATION2 = [0,1];
// Where you want the THIRD date time stamp offset from the input location. [row, column]
var DATETIMELOCATION3 = [0,1];
// Sheet you are working on
var SHEETNAME = 'PM AUGUST 2022'
function onEdit(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
// var notesSheet = ss.getSheets()[0];
// var values_range = sheet.getRange("L2:L" + sheet.getLastRow());
// var notes_range = sheet.getRange("Z2:Z" + sheet.getLastRow());
// var notes = notes_range.getValues();
//checks that we're on the correct sheet.
if( sheet.getSheetName() == SHEETNAME ) {
var lastRow = sheet.getLastRow();
var selectedCell = ss.getActiveCell();
//checks the column to ensure it is on the one we want to cause the date to appear.
if( selectedCell.getColumn() == COLUMNTOCHECK && selectedCell.getValue() == "Billing") {
var dateTimeCell = selectedCell.offset(DATETIMELOCATION[0],DATETIMELOCATION[1]);
dateTimeCell.setValue(new Date());
}
var selectedCell2 = ss.getActiveCell();
if (selectedCell2.getColumn()==COLUMNTOCHECK2 && selectedCell2.getValue() !== ""){
var dateTimeCell2 = selectedCell2.offset(DATETIMELOCATION2[0],DATETIMELOCATION2[1]);
dateTimeCell2.setValue(new Date());
}
var selectedCell3 = ss.getActiveCell();
//var notesSheet = ss.getSheets()[0];
var values_range = sheet.getRange("J2:J" + lastRow);
var notes_range = sheet.getRange("L2:L" + lastRow);
var notes = notes_range.getValues();
var oldValue = notes.oldValue;
if (selectedCell3.getColumn()==COLUMNTOCHECK3 && selectedCell3.getValue() !== ""){
var dateTimeCell3 = selectedCell3.offset(DATETIMELOCATION3[0],DATETIMELOCATION3[1]);
dateTimeCell3.setValue(new Date());
if (oldValue != null) {
notes += 'Prior Entry :' + oldvalue;
}
values_range.setNotes(notes);
}
}
}
I think this is what you were shooting for
function onEdit(e) {
var sh = e.range.getSheet();
if (sh.getName() == 'PM AUGUST 2022') {
var lastRow = sh.getLastRow();
if (e.range.columnStart == 22 && e.value == "Billing") {
e.range.offset(0, 1).setValue(new Date());
}
if (e.range.columnStart == 18 && e.value !== "") {
e.range.offset(0, 1).setValue(new Date());
}
if (e.range.columnStart == 10 && e.value !== "") {
e.range.offset(0, 1).setValue(new Date());
let ts = Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "yyyy MM dd HH:mm:ss")
sh.getRange(e.range.rowStart, 10).setNote(sh.getRange(e.range.rowStart, 10).getNote() + '\n' + sh.getRange(e.range.rowStart, 10).getValue() + ts);
}
}
}
Demo:
I have a script where I am trying to send emails, and I have almost everything figured out. The last piece is actually selecting the email string from a list of rules.
I want to get the emails (col C) if curSheet.getSheetName() = value in colA AND newVal = value in colB. I am having trouble combining if statements & for loops.
Here is a link to a sample Notification Rules sheet:
[https://docs.google.com/spreadsheets/d/1Cn9R3f85xF_QnOU_BNIvpqRq8FUvRGFaCUnTH3jyCTs/edit#gid=0]
function emailUpdate(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet()
var curSheet = ss.getActiveSheet()
var notifSheets = ["Sheet1", "Sheet2"]
var newVal = e.value
var row = e.range.rowStart
var curDate = Utilities.formatDate(new Date(), "GMT+1", "MM/dd/yyyy")
var hosp = curSheet.getRange(row,4).getValue()
var sheetID = curSheet.getSheetId()
var sheetURL = "linktodoc"+sheetID
var rules = ss.getSheetByName('Notification Rules')
var data = rules.getDataRange().getValues()
//Build Email
var subject = subjecttext
var intro = introtext
var closer = closingtext
if (e.range.columnStart == 2 && notifSheets.indexOf(curSheet.getSheetName()) != -1 && newVal !== undefined) {
console.log("It worked")
curSheet.getRange(row,3).setValue(curDate) //inserts edit date
var email = //some for / if statement to get colC if colA&colB match curSheet & newVal
GmailApp.sendEmail(email,subject,'', {htmlBody: intro+closer})
} else {
console.log("It didn't work")
}
}
I'm trying to have TIMEVALUE set off a script. I have column P which is a Timer column that is subtracting a time from NOW to get a time value and then I am converting that to a TIMEVALUE in column O. When that value is either below or above certain values I want column N to then have a value so it would trigger my checkboxes script. But for some reason I can't get the TIMEVALUE to trigger. I tried to put it in another column and have the values CopyAndPasted Values Only into a column with that run on a time trigger of every minute, but apparently that doesn't count as an Edit or it's not reading the DisplayValue.
function onEdit(e) {
timeValue(e);
checkboxes(e);
rangerTime(e);
}
function rangerTime(e){
var editRow = e.range.getRow();
var editColumn = e.range.getColumn();
if (editColumn === 13 && e.value == "TRUE") {
const sh = e.range.getSheet();
sh.getRange(editRow, 25).setValue(sh.getRange(editRow, 25).getValue()+1);
sh.getRange(editRow, 13).setValue(" ")
}
}
function timeValue(e) {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var sheet = spreadsheet.getActiveSheet();
var editRow = e.range.getRow();
var editColumn = e.range.getColumn();
if(editRow > 3) {
var rowRange = sheet.getRange("O" + editRow);
var kCell = sheet.getRange("K" + editRow);
var kValue = kCell.getValue();
var nCell = sheet.getRange("N" + editRow);
var kHasValue = kValue != "";
if(editColumn > 10) {
if(rowRange.getDisplayValue()<0.02 && !kHasValue){
nCell.setValue(1);
}
if (rowRange.getDisplayValue()>0.5 && !kHasValue){
nCell.setValue(1);
}
if(kHasValue) {
nCell.setValue(" ");
}
}
}
}
function checkboxes(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
ui = SpreadsheetApp.getUi();
var names = ss.getRange("N4:N");
var namesValues = names.getValues();
var checkboxes = ss.getRange("M4:M");
var cbRows = checkboxes.getHeight();
var cbValues = checkboxes.getValues();
var newCBValues = new Array(cbRows);
for (var row = 0; row < cbRows; row++) {
newCBValues[row] = new Array(0);
if (namesValues[row] == "" || namesValues[row] == " ") {
newCBValues[row][0] = " ";
}else{
if (cbValues[row][0] === true) {
newCBValues[row][0] = true;
}else{
newCBValues[row][0] = false;
}
}
}
checkboxes.setValues(newCBValues);
}
This part is run on the every minute Time Trigger:
function CopyandPaste() {
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.getRange('AA4').activate();
var currentCell = spreadsheet.getCurrentCell();
spreadsheet.getSelection().getNextDataRange(SpreadsheetApp.Direction.DOWN).activate();
currentCell.activateAsCurrentCell();
spreadsheet.getRange('O4').activate();
currentCell = spreadsheet.getCurrentCell();
spreadsheet.getSelection().getNextDataRange(SpreadsheetApp.Direction.DOWN).activate();
currentCell.activateAsCurrentCell();
spreadsheet.getRange('AA4:AA202').copyTo(spreadsheet.getActiveRange(), SpreadsheetApp.CopyPasteType.PASTE_VALUES, false);
};
Column AA = TIMEVALUE(Column P)
From the question
I tried to put it in another column and have the values CopyAndPasted Values Only into a column with that run on a time trigger of every minute, but apparently that doesn't count as an Edit or it's not reading the DisplayValue.
You are right, only actions done by a user directly through the Google Sheets user interface will cause the edit trigger to be triggered.
One option is to make that your time-driven trigger besides changing the checkboxes also call the timeValue function but you have to refactor it or to mock the edit event object to pass it as the timeValue parameter.
Reference
https://developers.google.com/apps-script/guides/triggers
I have three dropdown boxes that are dependent on each other. The code works fine but is really slow, 3 seconds to update the next box. I'm used to VBA and this is no problem in Excel, but it's really show in Google Sheets. I added a Browser.msgBox("here"); to the first line of the code and from changing the dropdown it takes 2 seconds to display it. This can't be normal?
Here's the code: (The list options are stored in Named Ranges, the correct name is generated and stored in the optName below )
function onEdit(event) {
if (!event) return;
//SETTINGS
var ss = event.source;
var sheet = event.source.getActiveSheet();
var r = event.source.getActiveRange();
var sheetName = sheet.getName()
if(sheet.getName() == 'Damages & Repairs July 2018') {
var lcol = 3; // leftmost column number you are monitoring
var rcol =6; // rightmost column number you are monitoring
}else{
var lcol = 4; // leftmost column number you are monitoring
var rcol =7; // rightmost column number you are monitorin
}
//
if (sheetName == 'Refunds July 2018' || sheetName == 'Exchanges July 2018'|| sheetName == 'Damages & Repairs July 2018') {
var sourceCol = r.getColumn();
var sourceRow = r.getRow();
var ss = SpreadsheetApp.getActiveSpreadsheet();
if(sheetName == 'Damages & Repairs July 2018') {
if(sourceCol == 4) {
var optName = sheet.getRange(sourceRow, lcol ).getValue() + sheet.getRange(sourceRow, lcol + 1).getValue();
} else if ( sourceCol == 5) {
var optName = sheet.getRange(sourceRow, lcol + 1).getValue();
} else {
var optName = sheet.getRange(sourceRow, sourceCol).getValue();
}
} else {
if(sourceCol == 5) {
var optName = sheet.getRange(sourceRow, lcol ).getValue() + sheet.getRange(sourceRow, lcol + 1).getValue();
} else if ( sourceCol == 6) {
var optName = sheet.getRange(sourceRow, lcol + 1).getValue();
} else {
var optName = sheet.getRange(sourceRow, sourceCol).getValue();
}
}
var optName = optName.replace(/\s/g, "");
var tableRange = ss.getRangeByName(optName).getValues();
var optList = "";
if (sourceCol >= lcol && sourceCol <= rcol) {
// define the dropdown/validation rules
var rangeRule = SpreadsheetApp.newDataValidation().requireValueInList(tableRange);
// Clear the validation
//sheet.getRange(sourceRow, sourceCol + 1).clearDataValidations()
//sheet.getRange(sourceRow, sourceCol + 1).clearContent()
// set the dropdown validation for the row
sheet.getRange(sourceRow, sourceCol + 1).setDataValidation(rangeRule); // set range to your range
}
}
}
Any help would be very much appreciated.
Thanks
Chris
UPDATE
I changed the code to this - but still not that much faster:
function onEdit(event) {
//SETTINGS
var ss = event.source;
var sheet = event.source.getActiveSheet();
var r = event.source.getActiveRange();
var sheetName = sheet.getName();
var sourceCol = r.getColumn();
var sourceRow = r.getRow();
if(sheet.getName() == 'Damages & Repairs July 2018') {
var lcol = 3; // leftmost column number you are monitoring
var rcol =6; // rightmost column number you are monitoring
}else{
var lcol = 4; // leftmost column number you are monitoring
var rcol =7; // rightmost column number you are monitorin
}
var sourceRange = sheet.getRange('R' + sourceRow + 'C' + lcol + ':R' + sourceRow + 'C' + rcol).getValues();
if (sheetName == 'Refunds July 2018' || sheetName == 'Exchanges July 2018'|| sheetName == 'Damages & Repairs July 2018') {
var ss = SpreadsheetApp.getActiveSpreadsheet();
if(sheetName == 'Damages & Repairs July 2018') {
if(sourceCol == 4) {
var optName = sourceRange[0][0] + sourceRange[0][1];
} else if ( sourceCol == 5) {
var optName = sourceRange[0][1];
} else {
var optName =sourceRange[0][0];
}
} else {
if(sourceCol == 5) {
var optName = sourceRange[0][0] + sourceRange[0][1];
} else if ( sourceCol == 6) {
var optName = sourceRange[0][1];
} else {
var optName = sourceRange[0][0];
}
}
var optName = optName.replace(/\s/g, "");
var tableRange = ss.getRangeByName(optName).getValues();
var optList = "";
if (sourceCol >= lcol && sourceCol <= rcol) {
// define the dropdown/validation rules
var rangeRule = SpreadsheetApp.newDataValidation().requireValueInList(tableRange);
// Clear the validation
sheet.getRange(sourceRow, sourceCol + 1).clearDataValidations()
sheet.getRange(sourceRow, sourceCol + 1).clearContent()
// set the dropdown validation for the row
sheet.getRange(sourceRow, sourceCol + 1).setDataValidation(rangeRule); // set range to your range
}
}
}