I created a library ('CustomerReportLibrary') that has a few functions for which I created installable onEdit triggers from the spreadsheet script.
Two specific functions from this library don't work (the others do..). Can anybody shed light about the reason for that?
The functions that don't work:
function restoreRangeSettings(e) {
// retrieves data validation, conditional formatting and formatting settings from template
var spreadsheet = SpreadsheetApp.getActive();
var range = e.range;
var sheet = range.getSheet();
var A1Notation = range.getA1Notation();
var templateRange = spreadsheet.getSheetByName(sheet.getName() + ' design').getRange(A1Notation);
templateRange.copyTo(range, SpreadsheetApp.CopyPasteType.PASTE_FORMAT, false);
templateRange.copyTo(range, SpreadsheetApp.CopyPasteType.PASTE_CONDITIONAL_FORMATTING, false);
templateRange.copyTo(range, SpreadsheetApp.CopyPasteType.PASTE_DATA_VALIDATION, false);
}
function logTimestamp(e) {
// logs task last edit timestamp
var spreadsheet = SpreadsheetApp.getActive();
var sheet = e.range.getSheet();
var editedCell = sheet.getActiveCell();
var editedRow = editedCell.getRow();
var editedColumn = editedCell.getColumn();
var emailCell = sheet.getRange(editedRow,5);
var dateCell = sheet.getRange(editedRow,4);
var time = new Date();
time = Utilities.formatDate(time, "GMT+02", "dd/MM/yyyy HH:mm:ss");
// don't do anything if edited sheets are not task sheets
if ((sheet.getName() == 'instructions') || (sheet.getName() == 'generate report')) {return;}
// logs timestamp when the edited range is within the task documentation table
if ((editedColumn() > 7) && (editedRow > 4)) {
// adds user and month to also to next row when task is added
if ((sheet.getRange(editedRow,5,1,3).getValues() != '') && (editedColumn == 8) && (sheet.getRange(editedRow+1,4).isBlank())) {
sheet.getRange(editedRow+1,5,1,3).setValues(sheet.getRange(editedRow,5,1,3).getValues());
}
dateCell.setValue(time);
}
}
The code in the spreadsheet script:
function EditTriggers() {
var customerSsId = SpreadsheetApp.getActive().getId();
var customerSpreadsheet = SpreadsheetApp.openById(customerSsId);
ScriptApp.newTrigger('CustomerReportLibrary.restoreRangeSettings')
.forSpreadsheet(customerSpreadsheet)
.onEdit()
.create();
ScriptApp.newTrigger('CustomerReportLibrary.logTimestamp')
.forSpreadsheet(customerSpreadsheet)
.onEdit()
.create();
//works fine
ScriptApp.newTrigger('CustomerReportLibrary.showRowsAndComments')
.forSpreadsheet(customerSpreadsheet)
.onEdit()
.create();
//works fine
ScriptApp.newTrigger('CustomerReportLibrary.optShortcuts')
.forSpreadsheet(customerSpreadsheet)
.onEdit()
.create();
}
Thanks in advance!
I am happy to update that the issue is resolved!
First action (logTimestamp)
if ((editedColumn() > 7) && (editedRow > 4)) {
I accidently referred to editedColumn as a function and not a variable.
Second action (restoreRangeSettings)
I have chosen a different strategy for the required result, base on this answer. (See discussion about what I tried to achieve here.)
Third action (general)
I palyed a bit with variables defenition. For example - defining the range as "e.range" did not work, while defining it as "e.source.getActiveRange()" magically did.
Finally - the final code
function restoreRangeSettings(e) {
// retrieves data validation, conditional formatting and formatting settings from template
var editedSs = e.source;
var editedRange = editedSs.getActiveRange();
var editedSh = editedRange.getSheet().getName();
var templateSs = SpreadsheetApp.openById('10zVclYTCEOEwskUID4vxSPxKR_tb3RaR76eG7TfxLUE');
var templateSh = templateSs.getSheetByName(editedSh);
var copiedSh = templateSh.copyTo(editedSs).hideSheet();
var templateRange = editedSs.getSheetByName(copiedSh.getName()).getRange(editedRange.getA1Notation());
templateRange.copyTo(editedRange, SpreadsheetApp.CopyPasteType.PASTE_FORMAT, false);
templateRange.copyTo(editedRange, SpreadsheetApp.CopyPasteType.PASTE_CONDITIONAL_FORMATTING, false);
templateRange.copyTo(editedRange, SpreadsheetApp.CopyPasteType.PASTE_DATA_VALIDATION, false);
editedSs.deleteSheet(copiedSh);
}
function logTimestamp(e) {
// logs task last edit timestamp
var editedRange = e.range;
var editedSh = editedRange.getSheet();
var editedRow = editedRange.getRow();
var editedColumn = editedRange.getColumn();
var emailCell = editedSh.getRange(editedRow,5);
var dateCell = editedSh.getRange(editedRow,4);
var time = new Date();
time = Utilities.formatDate(time, "GMT+02", "dd/MM/yyyy HH:mm:ss");
// don't do anything if edited sheets are not task sheets
if ((editedSh.getName() == 'instructions') || (editedSh.getName() == 'generate report')) {return;}
// logs timestamp when the edited range is within the task documentation table
if ((editedColumn > 7) && (editedRow > 4)) {
// adds user and month to also to next row when task is added
if ((editedSh.getRange(editedRow,5,1,3).getValues() != '') && (editedColumn == 8) && (editedSh.getRange(editedRow+1,4).isBlank())) {
editedSh.getRange(editedRow+1,5,1,3).setValues(editedSh.getRange(editedRow,5,1,3).getValues());
}
dateCell.setValue(time);
}
}
Note: spreadsheet script did not change.
Related
The code works when one row is edited at a time. But I need to copy and paste multiple rows as well, and the timestamp only appears on the first row (where I paste it).
function onEdit(e) {
var range = e.range;
var spreadSheet = e.source;
var sheetName = spreadSheet.getActiveSheet().getName();
var row = range.getRow();
var activeSheetName = 'Analysis';
var statusColumnNum = 17;
var updateColumnNum = 15;
if(sheetName == activeSheetName)
{
if(spreadSheet.getActiveSheet().getRange(row,statusColumnNum).getValues() == 'No Further Action' || spreadSheet.getActiveSheet().getRange(row,statusColumnNum).getValues() == 'Invalid Code' || spreadSheet.getActiveSheet().getRange(row,statusColumnNum).getValues() == 'For BAF Filing'){
if(spreadSheet.getActiveSheet().getRange(row,updateColumnNum).getValue() == ''){
var new_date = new Date();
spreadSheet.getActiveSheet().getRange(row,updateColumnNum).setValue(new_date).setNumberFormat("MM/dd/yyyy hh:mm:ss A/P");
}
}
else{
spreadSheet.getActiveSheet().getRange(row,updateColumnNum).setValue('');
}
}
}
In your script, how about the following modification?
Modified script:
function onEdit(e) {
var range = e.range;
var spreadSheet = e.source;
var sheet = spreadSheet.getActiveSheet();
var sheetName = sheet.getName();
var activeSheetName = 'Analysis';
var statusColumnNum = 17;
var updateColumnNum = 15;
var texts = ['No Further Action', 'Invalid Code', 'For BAF Filing'];
if (sheetName == activeSheetName) {
var range = sheet.getRange(range.rowStart, statusColumnNum, range.rowEnd - range.rowStart + 1);
var values = range.getValues();
var new_date = new Date();
range.offset(0, updateColumnNum - statusColumnNum)
.setNumberFormat("MM/dd/yyyy hh:mm:ss A/P")
.setValues(values.map(([q]) => [texts.includes(q) ? new_date : null]));
}
}
In this case, when this script is run by copying and pasting the values, all edited rows are checked. By this, the date is inserted into your expected rows.
Reference:
map()
I made my function that it's working good for me now, but i have the issue to make it work on mobile so i tried another way to use the function OnEdit, so when it will be the value that i want to lunch the function that i created before, but for now it's not working and i don't know why it's not, i'm asking for you help with this small issue ;)
thank you
function onEdit(e) {
var range = e.range;
var spreadSheet = e.source;
var sheetName = spreadSheet.getActiveSheet().getName();
var column = range.getColumn();
var row = range.getRow();
var value = SpreadsheetApp.getActiveSheet().getRange(row, column).getValue();
if(sheetName == 'New Orders' && column == 12 && value=='COMMANDE VALIDER')
{
VALIDERCOMMANDE();
}
}
function VALIDERCOMMANDE() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var copySheet = ss.getSheetByName("New Orders");
var url = "https://docs.google.com/spreadsheets/d/1eTWG_XZt-3CMzkxgKM4pCvD41deGMdka37eQkHM9oDg/edit#gid=0";
var ss2 = SpreadsheetApp.openByUrl(url);
var pasteSheet = ss2.getSheetByName("Order Pull");
// get source range
var max = copySheet.getMaxRows().toString();
var range = copySheet.getRange(2, 1, max, 12);
var dataValues = range.getValues();
for (i = 1; i < dataValues.length; i++) {
if (dataValues[i][11] === 'COMMANDE VALIDER') {
pasteSheet.appendRow([dataValues[i][0],
dataValues[i][1],
dataValues[i][2],
dataValues[i][3],
dataValues[i][4],
dataValues[i][5],
dataValues[i][6],
dataValues[i][7],
dataValues[i][8],
dataValues[i][9],
dataValues[i][10],
dataValues[i][11]]);
var clearRow = i + 2;
copySheet.getRange('D' + clearRow + ':L' + clearRow).clearContent();
}
}
// get destination range
var destination = pasteSheet.getRange(pasteSheet.getLastRow() + 1, 1, max, 1);
// clear source values
Browser.msgBox('Commande Confirmer');
}
Try this way
function onMyEdit(e) {
const sh = e.range.getSheet();
if (sh.getName() == 'New Orders' && e.range.columnStart == 12 && e.value == 'COMMANDE VALIDER') {
VALIDERCOMMANDE();
}
}
function VALIDERCOMMANDE() {
var ss = SpreadsheetApp.getActive();
var csh = ss.getSheetByName("New Orders");
var id = "1eTWG_XZt-3CMzkxgKM4pCvD41deGMdka37eQkHM9oDg";
var ss2 = SpreadsheetApp.openById(id);
var psh = ss2.getSheetByName("Order Pull");
var vs = csh.getRange(2,1,csh.getLastRow() - 1, 12).getValues().filter(r => r[11] == 'COMMANDE VALIDER').filter(e => e);
psh.getRange(psh.getLastRow() + 1, 1, vs.length, 12).setValues(vs);
}
I often find that onEdits are not reliable on mobile and sometimes the trigger process has to be repeated
Class Browser, Class UI and SpreadsheetApp.toast don't work in the Google Sheets mobile apps (iOS and Android). By the other hand, instead of using a simple trigger you should use an installable trigger because SpreasheetApp.openByUrl method requires authorization to run.
Change the name of the onEdit function, remove Browser.msgBox('Commande Confirmer'); and create an installable on edit function calling the renamed function should make your script work on the mobile apps
If you really need to have a custom notification when the onEdit function finish, you might send write a message or image on certain range. If you use on edit installable trigger you might also send an email or call an external API.
Related
Executing Google Apps Script Functions from Mobile App
Google Apps Script toast messages don't appear for anonymous editors
why is my trigger status "Paused" when triggered from mobile
I have the code below in my project, that helps me autofill the timestamp in "datetime" column in "test" sheet.
I want it to work for other sheets too. But I couldn't get it to work. Any help?
var SHEET_NAME = 'test';
var DATETIME_HEADER = 'datetime';
function getDatetimeCol(){
var headers = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_NAME).getDataRange().getValues().shift();
var colindex = headers.indexOf(DATETIME_HEADER);
return colindex+1;
}
function onEdit() {
var ss = SpreadsheetApp.getActiveSheet();
var cell = ss.getActiveCell();
var datecell = ss.getRange(cell.getRowIndex(), getDatetimeCol());
if (ss.getName() == SHEET_NAME && cell.getColumn() == 1 && !cell.isBlank() && datecell.isBlank()) {
datecell.setValue(new Date()).setNumberFormat("yyyy-MM-dd hh:mm");
}
};
If you want to have an onEdit() trigger runs on multiple sheets use installable trigger.
Take your code and put it in a standalone script, not bounded to a sheet.
Then create function to setup onOpen Trigger
/**
* Creates a trigger for when a spreadsheet opens.
*/
function createSpreadsheetOnOpenTrigger() {
var id = 'YOUR_SHEET_ID';
var ss = SpreadsheetApp.openById(id);
ScriptApp.newTrigger('NAME_OF_FUNCTION_TO_RUN')
.forSpreadsheet(ss)
.onOpen()
.create();
}
reference : link
Then you just have to change id to setup trigger for all sheets you want the code run.
In the code take care to change to get infomration regarding celle and sheet from the event object :
function functionToRunOnEdit() {
var sheet = **e.range.getSheet()**;
var cell = **e.range**;
var name = sheet.getName();
var datecell = ss.getRange(cell.getRowIndex(), getDatetimeCol(sheet));
if (SHEET_NAMES.includes(name) && cell.getColumn() == 1 && !cell.isBlank() && datecell.isBlank()) {
datecell.setValue(new Date()).setNumberFormat("yyyy-MM-dd hh:mm");
}
};
Reference : link
I have also changed the variable name ss into sheet becausse it is a sheet but not a spreadsheet.
var SHEET_NAMES = ['test', 'test2'];
var DATETIME_HEADER = 'datetime';
function getDatetimeCol(sheet){
var headers = sheet.getDataRange().getValues().shift();
var colindex = headers.indexOf(DATETIME_HEADER);
return colindex+1;
}
function onEdit() {
var sheet = SpreadsheetApp.getActiveSheet();
var cell = sheet.getActiveCell();
var name = sheet.getName();
var datecell = ss.getRange(cell.getRowIndex(), getDatetimeCol(sheet));
if (SHEET_NAMES.includes(name) && cell.getColumn() == 1 && !cell.isBlank() && datecell.isBlank()) {
datecell.setValue(new Date()).setNumberFormat("yyyy-MM-dd hh:mm");
}
};
I use this script to set a timestamp when a cell is changed. But it triggers in all sheets, and I only want it to be triggered when I do a change in sheet "XXX"
function onEditDatoMaxboStart() {
var s = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("XXX"); //change this to the name of your sheet
var r = s.getActiveCell();
if( r.getColumn() != 29 ) {
var row = r.getRow();
var time = new Date();
time = Utilities.formatDate(time, "GMT+01:00", "dd.MM.yyyy hh.mm.ss");
SpreadsheetApp.getActiveSheet().getRange('AP' + row.toString()).setValue(time);
};
};
How do I lock it to only be triggered when I make a change in the sheet "XXX"?
You cannot lock onEdit triggers to be ran on specific Sheets. However, within your onEdit function, you can check whether the Sheet that has triggered the action is the one you are targeting. If it is not, you can just return from the function without really executing any action. Your modified function would look as follows (edited only the first two lines):
function onEditDatoMaxboStart(e) {
if (e.range.getSheet().getName() !== "XXX") return;
var s = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("XXX"); //change this to the name of your sheet
var r = s.getActiveCell();
if( r.getColumn() != 29 ) {
var row = r.getRow();
var time = new Date();
time = Utilities.formatDate(time, "GMT+01:00", "dd.MM.yyyy hh.mm.ss");
SpreadsheetApp.getActiveSheet().getRange('AP' + row.toString()).setValue(time);
};
};
Also, I suggest you check out this page on using events objects obtained in trigger functions.
I pretty much have the Apps Script file working as intended. However, each time it is triggered, it also adds a second line where the sessionEmail is not captured and is blank.
function onEdit() {
var sessionEmail = Session.getActiveUser().getEmail();
var spreadsheetTimeZone = SpreadsheetApp.getActive().getSpreadsheetTimeZone();
var lastUpdatedString = Utilities.formatDate(new Date(), spreadsheetTimeZone, "MM/dd/yyyy' 'HH:mm:ss");
var s = SpreadsheetApp.getActiveSheet();
if (s.getName() == "Workload") { //checks that we're on the correct sheet
var r = s.getActiveCell();
if (r.getColumn() == 14) { //checks the column
var status = r.getValue();
var note = r.offset(0, -1);
var noteValue = note.getValue()
var delivery = r.offset(0, -5);
var deliveryValue = delivery.getValue()
}
// Validating fields are filled in
if (status == "Complete") {
var ui = SpreadsheetApp.getUi();
if (noteValue == '') { // if no note is entered, stop script with message box
var noStatus = ui.alert(
'Warning!',
'Please enter notation before choosing Complete.',
ui.ButtonSet.OK);
r.setValue('')
return;
}
// get destination range
var array = [lastUpdatedString, sessionEmail, deliveryValue, noteValue]
var ss = SpreadsheetApp.getActiveSpreadsheet();
var pasteSheet = ss.getSheetByName("Historical Notes Sheet");
pasteSheet.appendRow(array)
// clear response row
note.setValue('')
r.setValue('')
}
}
}
Image of what the results look like:
If any of you have any ideas on how to resolve this, and only append 1 line that has all the values, I would really appreciate it.
Thanks!
Update:
Logging the variable produces the following (expected) results, where the e-mail address appeared in the string.
Results still populating 2 rows:
Thanks for helping me troubleshoot.
I went into View>Executions, and noticed that the script was running twice each time at about 2 milliseconds apart. I think the function name onEdit() was acting like an On Edit trigger, and causing the script to run with another On Edit trigger that I had set up for it.
I tried removing the trigger, and leaving it with the onEdit()name, but that was causing it to not grab the sessionEmail. Changing the code to the below, and adding the trigger back causes the code to run as expected.
function appendLine() {
var sessionEmail = Session.getActiveUser().getEmail().toString();
var spreadsheetTimeZone = SpreadsheetApp.getActive().getSpreadsheetTimeZone();
var lastUpdatedString = Utilities.formatDate(new Date(), spreadsheetTimeZone, "MM/dd/yyyy' 'HH:mm:ss");
var s = SpreadsheetApp.getActiveSheet();
if (s.getName() == "Workload") { //checks that we're on the correct sheet
var r = s.getActiveCell();
if (r.getColumn() == 14) { //checks the column
var status = r.getValue();
var note = r.offset(0, -1);
var noteValue = note.getValue()
var delivery = r.offset(0, -5);
var deliveryValue = delivery.getValue().toString();
}
// Validating fields are filled in
if (status == "Complete") {
var ui = SpreadsheetApp.getUi();
if (noteValue == '') { // if no note is entered, stop script with message box
var noStatus = ui.alert(
'Warning!',
'Please enter notation before choosing Complete.',
ui.ButtonSet.OK);
r.setValue('')
return;
}
// get destination range
var array = [lastUpdatedString, sessionEmail, deliveryValue, noteValue]
var ss = SpreadsheetApp.getActiveSpreadsheet();
var pasteSheet = ss.getSheetByName("Historical Notes Sheet");
pasteSheet.appendRow(array)
// clear response row
note.setValue('')
r.setValue('')
}
}
}
Thanks again to everyone for your help!