I have this script that works perfectly in some of my spreadsheets but fails on others.
I have code that is too long, or crashes. Please help me shorten the code
Can someone help and explain me this?
Here is the code:
function setDataValid_(range, sourceRange) {
var rule = SpreadsheetApp.newDataValidation().requireValueInRange(sourceRange, true).build();
range.setDataValidation(rule);
}
function onEdit() {
var aSheet = SpreadsheetApp.getActiveSheet();
var aCell = aSheet.getActiveCell();
var aColumn = aCell.getColumn();
if (aColumn == 4 && aSheet.getName() == 'Tháng 6') {
var range = aSheet.getRange(aCell.getRow(), aColumn + 1);
var sourceRange = SpreadsheetApp.getActiveSpreadsheet().getRangeByName(aCell.getValue());
setDataValid_(range, sourceRange)
}
else if (aColumn == 5 && aSheet.getName() == 'Tháng 6') {
var range = aSheet.getRange(aCell.getRow(), aColumn + 1);
var sourceRange = SpreadsheetApp.getActiveSpreadsheet().getRangeByName(aCell.getValue());
setDataValid_(range, sourceRange)
}
if (aColumn == 4 && aSheet.getName() == 'Tháng 7') {
var range = aSheet.getRange(aCell.getRow(), aColumn + 1);
var sourceRange = SpreadsheetApp.getActiveSpreadsheet().getRangeByName(aCell.getValue());
setDataValid_(range, sourceRange)
}
else if (aColumn == 5 && aSheet.getName() == 'Tháng 7') {
var range = aSheet.getRange(aCell.getRow(), aColumn + 1);
var sourceRange = SpreadsheetApp.getActiveSpreadsheet().getRangeByName(aCell.getValue());
setDataValid_(range, sourceRange)
}
if (aColumn == 4 && aSheet.getName() == 'Tháng 8') {
var range = aSheet.getRange(aCell.getRow(), aColumn + 1);
var sourceRange = SpreadsheetApp.getActiveSpreadsheet().getRangeByName(aCell.getValue());
setDataValid_(range, sourceRange)
}
else if (aColumn == 5 && aSheet.getName() == 'Tháng 8') {
var range = aSheet.getRange(aCell.getRow(), aColumn + 1);
var sourceRange = SpreadsheetApp.getActiveSpreadsheet().getRangeByName(aCell.getValue());
setDataValid_(range, sourceRange)
}
if (aColumn == 4 && aSheet.getName() == 'Tháng 9') {
var range = aSheet.getRange(aCell.getRow(), aColumn + 1);
var sourceRange = SpreadsheetApp.getActiveSpreadsheet().getRangeByName(aCell.getValue());
setDataValid_(range, sourceRange)
}
else if (aColumn == 5 && aSheet.getName() == 'Tháng 9') {
var range = aSheet.getRange(aCell.getRow(), aColumn + 1);
var sourceRange = SpreadsheetApp.getActiveSpreadsheet().getRangeByName(aCell.getValue());
setDataValid_(range, sourceRange)
}
}
Here is an prototype of how I would do it. I can't guarantee it because your date set if pretty complex.
I use the event object e to identify the active sheet, range and value.
Notice I use Range.offset() because you want to set the value in the column next to the one edited.
function onEdit(e) {
let sheets = ['Tháng 6','Tháng 7','Tháng 8','Tháng 9'];
let sheet = e.range.getSheet();
if( sheets.indexOf(sheet.getName()) < 0 ) return;
let column = e.range.getColumn();
if( ( column === 4 ) || ( column === 5 ) ) {
let range = e.range.offset(0,1);
let sourceRange = e.source.getRangeByName(e.value);
setDataValid_(range, sourceRange);
}
}
Related
enter image description here
I want to create a filter area in A1 and A2 cells so that when I put drivers' name on these cells, the table (A:C) should be filtered according to the value entered in A1 and A2. Can someone explain me how to write Apps Script to perform such function?
Thank you in advance!
Try, with both names in C1 and C2 (pls, change sheet name as necessary)
function onEdit(e) {
var col = 3
var sh = e.source.getActiveSheet()
if (sh.getName() != "Sheet1") return;
if (e.range.columnStart > col || e.range.columnEnd < col) return;
if (e.range.rowStart > 2) return;
var names = sh.getRange(1, col, 2, 1).getValues().flat()
var range = sh.getRange(3, col, sh.getLastRow() - 2, sh.getLastColumn() - col + 1);
var filter = sh.getFilter();
if (filter !== null) filter.remove();
if (countNotOccurrences(names, '') == 0) return;
var hiddenNames = range.getValues().slice(1).map(row => row[0]).filter(who => names.indexOf(who) == -1);
range.createFilter();
var criteria = SpreadsheetApp.newFilterCriteria().setHiddenValues([...new Set(hiddenNames)]).build();
sh.getFilter().setColumnFilterCriteria(col, criteria);
}
const countNotOccurrences = (arr, val) => arr.reduce((a, v) => (v !== val ? a + 1 : a), 0);
Driver Data
function MyFunction() {
const ss = SpreadsheetApp.getActive()
const sh = ss.getSheetByName('Sheet0');
const osh = ss.getSheetByName('Sheet1');
osh.clearContents();
const sr = 3;
const dA = sh.getRange(1,1,2).getValues().flat();
const name = dA[0] + ' ' + dA[1];
const nameColumn = 1;
const vo = sh.getRange(sr,1,sh.getLastRow() - sr + 1, sh.getLastColumn()).getValues().filter(r => r[nameColumn -1] == name);
osh.getRange(1,1,vo.length,vo[0].length).setValues(vo);
}
It can be something like this probably:
function onEdit(e) {
if (e.range.columnStart > 1) return;
if (e.range.rowStart > 2) return;
filter_table(e.value);
}
function filter_table(name) {
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getRange(3,1,sheet.getLastRow(),3);
var data = range.getValues();
var hidden_values = data.map(x => x[0]).filter(x => x != name);
var filter = sheet.getFilter();
if (filter !== null) filter.remove();
range.createFilter();
var criteria = SpreadsheetApp.newFilterCriteria().setHiddenValues(hidden_values).build();
sheet.getFilter().setColumnFilterCriteria(1, criteria);
}
It filteres the range A3:C every time you edit cell A1 or cell A2.
But I'm not sure though what do you want to get when the two cells contain different names.
Update
Here is the 'multi-filter' solution :
function onEdit(e) {
if (e.range.columnStart > 1) return;
if (e.range.rowStart > 2) return;
var sheet = SpreadsheetApp.getActiveSheet();
if(sheet.getName() != 'Filter') return; // <-- to limit the trigger only one sheet 'Filter'
var range = sheet.getRange(1,1,sheet.getLastRow(),3);
var [name1, name2, ...data] = range.getValues();
var hidden_values = data.map(x => x[0])
.filter(x => x != name1[0] && x != name2[0]);
var filter = sheet.getFilter();
if (filter !== null) filter.remove();
range.offset(2,0).createFilter();
if (name1[0] + name2[0] == '') return;
var criteria = SpreadsheetApp.newFilterCriteria()
.setHiddenValues(hidden_values).build();
sheet.getFilter().setColumnFilterCriteria(1, criteria);
}
If both of the cells are empty you will see all the rows.
I'm trying to insert a timestamp into a cell in the "timestamp" column with the same row index as the edited or pasted value in the "status" column, only insert a timestamp in to an empty cell in "timestamp" column, while skipping the cell that already has a value in the "timestamp" column.
I'd also like to convert the date to a number, such as yymmddHHmmss*1000 + seri number column "No." Exp: If the timestamp is 22:01:20 14:08:05 and the sequence number in column "No." is 678, then value I want to insert into column "ID" is 2201201408050678.
I need to use the column header as a reference in this code to ensure that the function works correctly when the column index changes.
Issue: When changing many rows, this code simply repeats the original function for each selected cell. It gets the job done, but not too quickly.
How can I improve code speed when a multi-row update occurs?
No.
Timestamp
ID
Status
20
24:01:22 15:01:30
2201241501300020
Approved
17
Process
16
24:01:22 15:59:10
2201241559100016
Approved
16
function neworder2_onEdit(e) {
var sheet = e.range.getSheet();
if ((sheet.getSheetName() == 'RETAIL_ORDER') || (sheet.getSheetName() == 'HAMPER_ORDER') || (sheet.getSheetName() == 'SEA FOOD_ORDER') || (sheet.getSheetName() == 'GARDEN_ORDER'))
{
var col = e.range.columnStart;
var col_header = sheet.getRange(1,col).getValue();
if (col_header != 'Status') return;
var headers = sheet.getRange(1,1,1,sheet.getLastColumn()).getValues()[0];
var timestamp_col = headers.indexOf('Timestamp') + 1;
var num_col = headers.indexOf('No.') + 1;
var id_col = headers.indexOf('ID') + 1;
var row_start = e.range.rowStart;
var row_end = e.range.rowEnd;
if (sheet.getRange(row_start,col).getValue() != 'Approved') return;
var tz = SpreadsheetApp.getActiveSpreadsheet().getSpreadsheetTimeZone();
var timestamp = Utilities.formatDate(new Date(), tz, 'yy-MM-dd HH:mm:ss');
for (let row = row_start; row <= row_end; row++) {
var timestamp_cell = sheet.getRange(row, timestamp_col);
if (timestamp_cell.getValue() !== '') continue;
timestamp_cell.setValue(timestamp).setNumberFormat('yy:MM:dd HH:mm:ss');
var num = sheet.getRange(row,num_col).getValue().toString().padStart(4,'0');
var id = timestamp.replace(/\D/g,'') + num;
var id_cell = sheet.getRange(row,id_col);
id_cell.setValue(id);
}
}
}
I believe your goal is as follows.
Your script works fine. You want to reduce the process cost of the script.
In this case, how about the following modification?
Modified script:
function neworder2_onEdit(e) {
var sheet = e.range.getSheet();
if (['RETAIL_ORDER', 'HAMPER_ORDER', 'SEA FOOD_ORDER', 'GARDEN_ORDER'].includes(sheet.getSheetName())) { // Modified
var col = e.range.columnStart;
var col_header = sheet.getRange(1, col).getValue();
if (col_header != 'Status') return;
var row_start = e.range.rowStart;
var row_end = e.range.rowEnd;
// I modified below script.
var values = sheet.getRange(row_start, 1, row_end - row_start + 1, 4).getValues();
if (!values.some(r => r[3] == 'Approved')) return;
var tz = e.source.getSpreadsheetTimeZone();
var timestamp = Utilities.formatDate(new Date(), tz, 'yy-MM-dd HH:mm:ss');
var res = values.map(([a, b, c]) => (a == "" || b != "") ? [b, c] : [timestamp, timestamp.replace(/\D/g, '') + a.toString().padStart(4, '0')]);
sheet.getRange(row_start, 2, row_end - row_start + 1, 2).setValues(res);
sheet.getRange(row_start, 2, row_end - row_start + 1, 1).setNumberFormat('yy:MM:dd HH:mm:ss');
}
}
In this modification, after the array was created using the script in your for loop, the array was put to the sheet.
Reference:
map()
Added 1:
From your following replying,
When "status", "ID" or "timstamp" column index change, in case I want to insert column then our code not working. Can we use column header (status, timestamp, ID, No.) as pramameter for my cript? Can you give suggestion to do this?
In this case, how about the following sample script?
Sample script:
function onEdit(e) {
var sheet = e.range.getSheet();
if (['RETAIL_ORDER', 'HAMPER_ORDER', 'SEA FOOD_ORDER', 'GARDEN_ORDER'].includes(sheet.getSheetName())) { // Modified
var col = e.range.columnStart;
var header = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0].map(h => h.toLowerCase()); // Added
var obj = header.reduce((o, e, i) => (o[e] = i, o), {});
var col_header = header[col - 1]; // Modified
if (col_header != 'status') return;
var row_start = e.range.rowStart;
var row_end = e.range.rowEnd;
// I modified below script.
var values = sheet.getRange(row_start, 1, row_end - row_start + 1, header.length).getValues();
if (!values.some(r => r[obj["status"]] == 'Approved')) return;
var tz = e.source.getSpreadsheetTimeZone();
var timestamp = Utilities.formatDate(new Date(), tz, 'yy-MM-dd HH:mm:ss');
var res = values.map(r => {
if (!(r[obj["no."]] == "" || r[obj["timestamp"]] != "")) {
r[obj["timestamp"]] = timestamp;
r[obj["id"]] = timestamp.replace(/\D/g, '') + r[obj["no."]].toString().padStart(4, '0');
}
return r;
});
sheet.getRange(row_start, 1, row_end - row_start + 1, res[0].length).setValues(res);
sheet.getRange(row_start, [obj["timestamp"]] + 1, row_end - row_start + 1).setNumberFormat('yy:MM:dd HH:mm:ss');
}
}
In this script, from your question, it supposes that the header values are No.,Timestamp,ID,Status. Please be careful this.
Added 2:
From your following new issue,
It's working but there is some issue with my sheet. when scipt ran that paste value to all column, some of theme using arrayformula so all column use arrayformula will get error "#REF!" Can we just paste value to column timestame, id.
In this case, how about the following sample script?
Sample script:
function onEdit(e) {
var sheet = e.range.getSheet();
if (['RETAIL_ORDER', 'HAMPER_ORDER', 'SEA FOOD_ORDER', 'GARDEN_ORDER'].includes(sheet.getSheetName())) { // Modified
var col = e.range.columnStart;
var header = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0].map(h => h.toLowerCase()); // Added
var obj = header.reduce((o, e, i) => (o[e] = i, o), {});
var col_header = header[col - 1]; // Modified
if (col_header != 'status') return;
var row_start = e.range.rowStart;
var row_end = e.range.rowEnd;
// I modified below script.
var values = sheet.getRange(row_start, 1, row_end - row_start + 1, header.length).getValues();
if (!values.some(r => r[obj["status"]] == 'Approved')) return;
var tz = e.source.getSpreadsheetTimeZone();
var timestamp = Utilities.formatDate(new Date(), tz, 'yy-MM-dd HH:mm:ss');
var res = values.map(r => {
if (r[obj["no."]] != "" && r[obj["timestamp"]] == "" && r[obj["status"]] == "Approved") {
r[obj["timestamp"]] = timestamp;
r[obj["id"]] = timestamp.replace(/\D/g, '') + r[obj["no."]].toString().padStart(4, '0');
}
r.shift();
return r;
});
sheet.getRange(row_start, 2, row_end - row_start + 1, res[0].length).setValues(res);
sheet.getRange(row_start, [obj["timestamp"]] + 2, row_end - row_start + 1).setNumberFormat('yy:MM:dd HH:mm:ss');
}
}
I'm trying to add timestamps to my datetime row on edit. What I'm getting right now is if one row is edited at a time it works. However, what I need is to timestamp every row when I copy/paste values in over multiple rows.
function getDatetimeCol(){
var SHEET_NAME = 'Queue';
var DATETIME_HEADER = 'datetime (+48h for archive)';
var headers = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_NAME).getDataRange().getValues().shift();
var colindex = headers.indexOf(DATETIME_HEADER);
return colindex+1;}
function onEdit(e) {
var SHEET_NAME = 'Queue';
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");
}
};
function onEdit(e) {
//e.source.toast('entry');
const sh = e.range.getSheet();
if (sh.getName() == 'Queue' && e.range.columnStart == 1) {
//e.source.toast('cond');
let col = {};
sh.getRange(1, 1, 1, sh.getLastColumn()).getValues()[0].forEach((h, i) => { col[h] = i + 1 });
for (var i = 0; i < e.range.rowEnd - e.range.rowStart + 1; i++) {
let rg = sh.getRange(e.range.rowStart + i, col['datetime (+48h for archive)']);
if (rg.isBlank() && sh.getRange(e.range.rowStart,1).getValue() != '') {
//e.source.toast('if');
rg.setValue(new Date()).setNumberFormat("yyyy-MM-dd hh:mm");
}
}
}
}
I'm using this code to move completed to one sheet but everything else from completed back to current. Is there a way to make it quicker like everything that isn't completed move back? Also is there a way to add sort by column 4 in the same code for the current sheet?
function onEdit() {
// moves a row from a sheet to another when a magic value is entered in a column
// adjust the following variables to fit your needs
// see https://productforums.google.com/d/topic/docs/ehoCZjFPBao/discussion
var sheetNameToWatch = "Current";
var columnNumberToWatch = 4;
var valueToWatch = "Completed";
var sheetNameToMoveTheRowTo = "Completed";
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getActiveCell();
if (sheet.getName() == sheetNameToWatch && range.getColumn() == columnNumberToWatch && range.getValue() == valueToWatch) {
var targetSheet = ss.getSheetByName(sheetNameToMoveTheRowTo);
var targetRange = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
sheet.getRange(range.getRow(), 1, 1, sheet.getLastColumn()).moveTo(targetRange);
sheet.deleteRow(range.getRow());
}
var sheetNameToWatch = "Completed";
var columnNumberToWatch = 4;
var valueToWatch = "At Risk";
var sheetNameToMoveTheRowTo = "Current";
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getActiveCell();
if (sheet.getName() == sheetNameToWatch && range.getColumn() == columnNumberToWatch && range.getValue() == valueToWatch) {
var targetSheet = ss.getSheetByName(sheetNameToMoveTheRowTo);
var targetRange = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
sheet.getRange(range.getRow(), 1, 1, sheet.getLastColumn()).moveTo(targetRange);
sheet.deleteRow(range.getRow());
}
var sheetNameToWatch = "Completed";
var columnNumberToWatch = 4;
var valueToWatch = "Hold";
var sheetNameToMoveTheRowTo = "Current";
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getActiveCell();
if (sheet.getName() == sheetNameToWatch && range.getColumn() == columnNumberToWatch && range.getValue() == valueToWatch) {
var targetSheet = ss.getSheetByName(sheetNameToMoveTheRowTo);
var targetRange = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
sheet.getRange(range.getRow(), 1, 1, sheet.getLastColumn()).moveTo(targetRange);
sheet.deleteRow(range.getRow());
}
var sheetNameToWatch = "Completed";
var columnNumberToWatch = 4;
var valueToWatch = "Placed";
var sheetNameToMoveTheRowTo = "Current";
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getActiveCell();
if (sheet.getName() == sheetNameToWatch && range.getColumn() == columnNumberToWatch && range.getValue() == valueToWatch) {
var targetSheet = ss.getSheetByName(sheetNameToMoveTheRowTo);
var targetRange = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
sheet.getRange(range.getRow(), 1, 1, sheet.getLastColumn()).moveTo(targetRange);
sheet.deleteRow(range.getRow());
}
var sheetNameToWatch = "Completed";
var columnNumberToWatch = 4;
var valueToWatch = "Safe";
var sheetNameToMoveTheRowTo = "Current";
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getActiveCell();
if (sheet.getName() == sheetNameToWatch && range.getColumn() == columnNumberToWatch && range.getValue() == valueToWatch) {
var targetSheet = ss.getSheetByName(sheetNameToMoveTheRowTo);
var targetRange = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
sheet.getRange(range.getRow(), 1, 1, sheet.getLastColumn()).moveTo(targetRange);
sheet.deleteRow(range.getRow());
}
var sheetNameToWatch = "Completed";
var columnNumberToWatch = 4;
var valueToWatch = "Other";
var sheetNameToMoveTheRowTo = "Current";
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getActiveCell();
if (sheet.getName() == sheetNameToWatch && range.getColumn() == columnNumberToWatch && range.getValue() == valueToWatch) {
var targetSheet = ss.getSheetByName(sheetNameToMoveTheRowTo);
var targetRange = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
sheet.getRange(range.getRow(), 1, 1, sheet.getLastColumn()).moveTo(targetRange);
sheet.deleteRow(range.getRow());
}
}
I have three scripts that are in a google docs spreadsheet. In this spreadsheet, in column H (or column 8), if I type an "x", the script changes it into that days date. After a few days, every date in column H has changed from a date to just a number. The numbers look like this: 40492, 40494, 40511. I am not sure what is causing this. Maybe it's something that is wrong in my script. I've pasted them below. Any ideas?
function onEdit(e) {
var colorA = "yellow";
var colorB = "#dddddd";
var colorC = "#dddddd";
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Purchase Orders");
var range = e.source.getActiveRange();
var sheetName = SpreadsheetApp.getActiveSheet().getName();
if (sheetName == "Purchase Orders") {
// 3 is column C
if (range.getColumn() == 3 && range.getValue() != "") {
sheet.insertRowAfter(range.getRow());
var r = range.getRow() + 1;
sheet.getRange("A" + r + ":H" + r).setBackgroundColor(colorC);
}
}
var col = e.source.getActiveRange().getColumn();
if(col == 3 || col == 8) {
var rows = sheet.getMaxRows();
//column C
var rangeC = sheet.getRange("C1:C"+rows);
var valuesC = rangeC.getValues();
//column H range
var rangeH = sheet.getRange("H1:H"+rows);
var colorH = rangeH.getBackgroundColors();
var valuesH = rangeH.getValues();
//iterate over each row in column C and H
//then change color
for (var row = 0; row < valuesC.length; row++) {
//check for columnC and column H
var hRow = colorH[row];
if (valuesC[row][0] != "" && valuesH[row][0] == "") {
hRow[0] = colorA;
} else if (valuesH[row][0] != "") {
hRow[0] = colorB;
}
}
sheet.getRange("H1:H" + rows).setBackgroundColors(colorH);
}
}
And this one
function onEdit(e) {
var ss = e.source.getActiveSheet();
var r = e.source.getActiveRange();
// 1 is A, 2 is B, ... 8 is H
if (r.getColumn() == 8 && r.getValue() == "x") {
r.setValue(Utilities.formatDate(new Date(), "MST", "yyyy-MM-dd"));
}
}
And this last one
ss = SpreadsheetApp.getActiveSpreadsheet();
function onOpen() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var menuEntries = [ {name: "New PO", functionName: "NewPO"}];
ss.addMenu("New PO", menuEntries);
}
function NewPO() {
SpreadsheetApp.getActiveSheet().insertRowsBefore(1,6);
// Adjust this range accordingly, these are the cells that will be
// copied. Format is getRange(startRow, startCol, numRows, numCols)
ss.getSheetByName("PO Form").getRange(1, 1, 6, 8)
.copyTo(SpreadsheetApp.getActiveSheet().getRange(1, 1, 6, 8));
}
In OnEdit, you probably want to set the format for that cell as well. setNumberFormat(numberFormat) appears to be the function you are after.
http://code.google.com/googleapps/appsscript/class_range.html#setNumberFormat