Combine two functions that must work one after another (with trigger) - google-apps-script

I have two working functions. One of them is myFunction with a trigger. Is protects the row of the cell when any information is entered in this cell in Column4.
function myFunction(e) {
const sheetNames = ['Sheet1', 'Sheet2', 'Sheet3']; // Please set the sheet names you want to run the script.
const range = e.range;
const sheet = range.getSheet();
const value = range.getValue();
const row = range.getRow();
if (!sheetNames.includes(sheet.getSheetName()) || range.getColumn() != 4 || row == 2 || value == "") return;
const p = sheet.getRange(`B${row}:D${row}`).protect();
const owner = Session.getActiveUser().getEmail();
p.getEditors().forEach(f => {
const email = f.getEmail();
if (email != owner) p.removeEditor(email);
});
}
Another function is an onEdit function. It adds date in Column1 when I enter information in Column4. The date appears in the same row with the cell in Column4.
function onEdit() {
var colToCheck = 4;
// Offset from the input [row, column]
var dateOffset = [0, -3];
// Sheets to proceed on
var sheetNames = ['Sheet1', 'Sheet2', 'Sheet3'];
var sheet = SpreadsheetApp.getActive().getActiveSheet();
var name = sheet.getName();
if (sheetNames.indexOf(name) > -1) {
var cell = sheet.getActiveCell();
var col = cell.getColumn();
if (col == colToCheck) {
var dateTimeCell = cell.offset(dateOffset[0], dateOffset[1]);
dateTimeCell.setValue(new Date());
}
}
}
How these two functions can be combined in one sheet?

I just added two separate functions to one sheet: the one with trigger and the one onEdit as two different codes. And they work as I need. So, we do not need to combine them somehow. They just work one after another. First, the one onEdit function works, and as it adds info to the necessary cell, the function with trigger starts working.

Related

Script is triggering but nothing happens?

I want to create a script that moves data from one sheet to another when I mark it as completed in a particular column. Using some other code I found on the internet, I have this, but when I go in and change that status to completed nothing happens. The trigger page in google apps script says it's executing, but it isn't doing anything to the actual sheet. Here is the code:
function onEdit(e) {
if(SpreadsheetApp.getActiveSpreadsheet() == "Planner" && e.value == "Completed"){ //If the edit was on Planner marking the Status "Completed"
var spr = SpreadsheetApp.getActiveSpreadsheet();
var myRange = e.range.offset(0,-3,0,3).getValue() //get the information from Planner
//find the first row of Calendar where completed assignments is blank
var column = spr.getRange('O:O');
var values = column.getValues(); // get all data in one call
var ct = 0;
while ( values[ct][0] != "" ) {
ct++;
ct++;
e.source.getSheetByName("Calendar").getRange(ct,15,1,3).setValues(myRange).getValues(); //copy the values from Planner to Calendar
e.source.getSheetByName("Planner").getRange(myRange).setValues("").getValues(); //delete values from Planner
;}
return (ct);
}
}
I assume something is wrong with it but I don't know what. I've never used apps script before so I honestly don't know what I'm doing. Here is the sheet:
Sheet
I want to move completed homework from the planner sheet to the calendar sheet when I change the status. Thanks so much for any help!!
EDIT:
I used lamblichus's code and it works great except that I still want to delete the data from the Planner Sheet after I move it. I tried this code and it didn't work:
function onEdit(e) {
const ss = e.source;
const range = e.range;
const sheet = range.getSheet();
if (sheet.getName() == "Planner" && e.value == "Completed") {
var otherData = range.offset(0,-3,1,3).getValues();
var currentClass = range.offset(0,-4).getMergedRanges()[0].getValue();
var [task,,date] = otherData[0];
var targetSheet = ss.getSheetByName("Calendar");
var targetRange = targetSheet.getRange("O1").getNextDataCell(SpreadsheetApp.Direction.DOWN).offset(1,0,1,3);
targetRange.setValues([[date,task,currentClass]]);
var initialSheet = ss.getSheetByName("Planner");
var initialRange = initialSheet.range.offset(0,-3,1,3);
initialRange.clearContent(); //delete values from Planner
}
}
Issues and solution:
There are several issues with your current code:
If you want to check the sheet name, you have to use Sheet.getName(). SpreadsheetApp.getActiveSpreadsheet() just returns the active spreadsheet, not sheet, and not its name anyway.
If you want to get values from multiple cells, you should use getValues(), not getValue().
The third parameter of offset corresponds to the number of rows of the resulting range. Therefore, it should not be 0.
The "Class" name is in a merged range, and only the top-left cell in a merged range includes the corresponding value. To get that value, you can use getMergedRanges and retrieve the first element in the resulting array. Since getValue() returns the value in the top-left cell of a range, it will return the "Class" name.
Code sample:
function onEdit(e) {
const ss = e.source;
const range = e.range;
const sheet = range.getSheet();
if (sheet.getName() == "Planner" && e.value == "Completed") {
var otherDataRange = range.offset(0,-3,1,3);
var otherData = otherDataRange.getValues();
var currentClass = range.offset(0,-4).getMergedRanges()[0].getValue();
var [task,,date] = otherData[0];
var targetSheet = ss.getSheetByName("Calendar");
var targetRange = targetSheet.getRange("O1").getNextDataCell(SpreadsheetApp.Direction.DOWN).offset(1,0,1,3);
targetRange.setValues([[date,task,currentClass]]);
otherDataRange.clearContent();
}
}
It looks like a syntax error on line 14, you put ;}, it should be }; you don't need to tell JavaScript (the coding language that AppScript is based on) when you end comments. But it likes it when you tell it when you end while loops.
Here is the updated code.
function onEdit(e) {
if(SpreadsheetApp.getActiveSpreadsheet() == "Planner" && e.value == "Completed"){ //If the edit was on Planner marking the Status "Completed"
var spr = SpreadsheetApp.getActiveSpreadsheet();
var myRange = e.range.offset(0,-3,0,3).getValue() //get the information from Planner
//find the first row of Calendar where completed assignments is blank
var column = spr.getRange('O:O');
var values = column.getValues(); // get all data in one call
var ct = 0;
while ( values[ct][0] != "" ) {
ct++;
ct++;
e.source.getSheetByName("Calendar").getRange(ct,15,1,3).setValues(myRange).getValues(); //copy the values from Planner to Calendar
e.source.getSheetByName("Planner").getRange(myRange).setValues("").getValues(); //delete values from Planner
};
return (ct);
};
}

Insert Timestamp when copy/paste whole row and value inserts/edits in Column C in google sheets

I tried inserting timestamp when a row is being copied and data inserts or edits in Column C same row cell, but it works only on manual entry, not on copy-paste.
Please suggest to me what I am missing or doing wrong.
function onChange() {
var s = SpreadsheetApp.getActiveSheet();
var sName = s.getName();
var r = s.getActiveCell();
var row = r.getRow();
var ar = s.getActiveRange();
var arRows = ar.getNumRows()
// Logger.log("DEBUG: the active range = "+ar.getA1Notation()+", the number of rows = "+ar.getNumRows());
if( r.getColumn() == 3 && sName == 'Sheet1') { //which column to watch on which sheet
// loop through the number of rows
for (var i = 0;i<arRows;i++){
var rowstamp = row+i;
SpreadsheetApp.getActiveSheet().getRange('F' + rowstamp.toString()).setValue(new Date()).setNumberFormat("MM/dd/yyyy hh:mm"); //which column to put timestamp in
}
}
}//setValue(new Date()).setNumberFormat("MM/dd/yyyy hh:mm:ss");
Use getLastColumn() to check whether column C is included in the pasted range.
Use getNumRows() to get the number of rows your copied range has, and so add the timestamp to all these rows.
No need to used an installed onChange() for this, a simple onEdit() is enough.
I'd also suggest to use event object in order to get information on which range was edited (even though this way you won't be able to fire this successfully from the script editor).
Edit: if you want to remove the timestamp when the range is cleared, you can just check that's the case, using every, or some, and clearContent if that's the case.
Code snippet:
function onEdit(e) {
var s = SpreadsheetApp.getActiveSheet();
var r = e.range;
var firstRow = r.getRow();
var numRows = r.getNumRows();
var firstCol = r.getColumn();
var lastCol = r.getLastColumn();
if((firstCol <= 3 || lastCol >= 3) && s.getName() == 'Sheet1') {
var emptyRange = r.getValues().every(row => row.every(value => value === ""));
var destRange = s.getRange(firstRow, 6, numRows);
if (emptyRange) destRange.clearContent();
else {
var dates = new Array(numRows).fill([new Date()]);
destRange.setValues(dates).setNumberFormat("MM/dd/yyyy hh:mm");
}
}
}
The following script will create timestamps starting from column F until the last column when you copy the row.
I think you are looking for this:
function onEdit(e) {
const startCol = 6; // column F
const s = e.source.getActiveSheet();
const sName = s.getName();
const ar = e.range;
const row = ar.getRow();
const arColumns = ar.getNumColumns();
const arRows = ar.getNumRows();;
if( sName == 'Sheet1') {
const rng = s.getRange(row,1,arRows,s.getMaxColumns());
check = rng.getValues().flat().every(v=>v=='');
if(check){
rng.clearContent();
}
else{
s.getRange(row,startCol,arRows,s.getMaxColumns()-startCol+1).setValue(new Date()).setNumberFormat("MM/dd/yyyy hh:mm");
}
}
}
Note:
Again, onEdit is a trigger function. You are not supposed to execute it manually and if you do so you will actually get errors (because of the use of the event object). All you have to do is to save this code snippet to the script editor and then it will be triggered automatically upon edits.

Sort by Named Range in Dropdown List

I am trying to create a sort function using Google Apps Script. I have about 20 columns in a Google sheet and want users to have the ability to sort the sheet by the click of a button rather than using the filter view because they keep on messing it up.
Rather than having 20 buttons for each column, I want one button with script which links to a dropdown list of Named Ranges being the same as the column headers.
Not sure if this is possible but this is a sample of my sheet:
I am struggling to get this script to work:
function sortByRangeName(rangeName){
var ss = SpreadsheetApp.getActive();
var sheet = ss.getSheetByName('Sheet 1');
var namedRange = ss.getRangeByName();
var startCol = namedRange.getColumn();
var lastCol = namedRange.getLastColumn();
var range = sheet.getRange('E1');
var value = range.getValue();
var rangeName = ss.getRangeByName(value);
var columnForSorting = (startCol <= dataRange.getLastColumn()) ? startCol : null;
if (namedRange && (startCol == lastCol) && columnForSorting) {
dataRange.sort({column: columnForSorting, ascending: false});
}
else {
throw new Error(Utilities.formatString("Range name: %s, startCol: %s, lastCol: %s, columnForSorting: %s", header, startCol, lastCol, columnForSorting));
}
}
This is a link to my spreadsheet:
Sample Spreadsheet
Try an Installable onEdit() with this function:
function sortByColumn(e) {
const sh=e.range.getSheet();
if(sh.getName()=='Sheet 1' && e.range.columnStart==5 && e.range.rowStart==1 && e.value) {
const hA=sh.getRange(2,1,1,sh.getLastColumn()).getValues()[0];
const col={};
hA.forEach(function(h,i){col[h]=i+1;});
const rg=sh.getRange(3,1,sh.getLastRow()-2,sh.getLastColumn());
rg.sort({column:col[e.value],ascending:true});
}
}
I believe your goal as follows.
You want to sort the range of "A3:E" with each column using the named ranges when the dropdown list at the cell "E1" on "Sheet 1" is selected.
Modification points:
In your script,
At var namedRange = ss.getRangeByName(), the argument is not used.
dataRange is not declared.
When above issues are resolved, the script works. But in this case, unfortunately, your goal cannot be achieve.
In order to achieve above goal, in this answer, the OnEdit event trigger is used.
In your dropdown list, it seems that there is the names including the space. Please be careful this. For this, I used trim().
Modified script:
Please copy and paste the following script to the script editor on the Google Spreadsheet. And, please select the dropdown list. By this, the values are sorted using the named ranges with the column selected by the dropdown list.
function onEdit(e) {
var range = e.range;
var sheet = range.getSheet();
if (sheet.getSheetName() != "Sheet 1" || range.getA1Notation() != "E1") return;
sheet.getDataRange().offset(2, 0).sort({
column: e.source.getRangeByName(e.value.trim()).getColumn(),
ascending: false
});
}
Note:
When the issues are removed from your script, it becomes as follows. I thought that knowing the modification points in your script might be help to study the script. So I also added this.
function sortByRangeName(rangeName){
rangeName = "Branch"; // This is a sample value
var ss = SpreadsheetApp.getActive();
var sheet = ss.getSheetByName('Sheet 1');
var namedRange = ss.getRangeByName(rangeName.trim()); // Modified
var startCol = namedRange.getColumn();
var lastCol = namedRange.getLastColumn();
var range = sheet.getRange('E1');
var value = range.getValue();
var rangeName = ss.getRangeByName(value);
var dataRange = sheet.getDataRange().offset(2, 0); // Added
var columnForSorting = (startCol <= dataRange.getLastColumn()) ? startCol : null;
if (namedRange && (startCol == lastCol) && columnForSorting) {
dataRange.sort({column: columnForSorting, ascending: false});
} else {
throw new Error(Utilities.formatString("Range name: %s, startCol: %s, lastCol: %s, columnForSorting: %s", header, startCol, lastCol, columnForSorting));
}
}
References:
Simple Triggers
Event Objects
getRangeByName(name)
trim()

How to refresh a filter onEdit() in Google sheets?

I have a sheet where I fill data with Hlookups depending on the values I choose in dropdowns.
I want to filter (hide) the rows that have a NULL or blank value in column 3 each time I change the values in the dropdowns (which changes the whole dataset).
If I create a normal filter, it doesn't refresh when the data changes.
var PARAMETER_ROW_NUMBER = 5; //The parameters goes from Row 1 to this Row
var PARAMETER_COLUMN_NUMBER = 2; //The column where the dropdowns with the parameters for the VLOOKUPs are
function onEdit()
{
var thisSheet = SpreadsheetApp.getActiveSheet();
if( thisSheet.getName() == "By Place" )
{
var cell = thisSheet.getActiveCell();
var cellRow = cell.getRow();
var cellColumn = cell.getColumn();
if( cellColumn == PARAMETER_COLUMN_NUMBER && cellRow <= PARAMETER_ROW_NUMBER)
{
setFilter(); // Execute the filter to clean null rows each time I change the values in the dropdowns
var rowDiff = PARAMETER_ROW_NUMBER - cellRow;
cell.offset( 1, 0, rowDiff).setValue(''); // As the parameters are dependent dropdowns, I clear the dropdowns if one changes
}
}
}
function setFilter()
{
var ss = SpreadsheetApp.getActiveSheet();
var rang = ss.getDataRange();
var filtercriteria = SpreadsheetApp.newFilterCriteria().setHiddenValues([' ','']).build();
var filter = rang.getFilter() || rang.createFilter();
filter.setColumnFilterCriteria(3, filtercriteria); // I want to hide the rows which has a null or blank in column 3
}
The setFilter() function doesn't work.
The array you're using to set the hidden values is not correct, try your code like this:
function setFilter()
{
var ss = SpreadsheetApp.getActiveSheet();
var rang = ss.getDataRange();
var filterCriteria = SpreadsheetApp
.newFilterCriteria()
.setHiddenValues(['NULL', ''])
.build();
var filter = rang.getFilter() || rang.createFilter();
filter.setColumnFilterCriteria(3, filterCriteria);
}
Also, if you want to see the logs from your onEdit executions, you can check them on your Apps Script file by clicking in View - > Executions, there you will be able to see the errors you are getting.
Docs
I used these docs to help you:
setColumnFilterCriteria(columnPosition, filterCriteria).
Class FilterCriteriaBuilder.

hide columns based on user input cell

hi all (I am new to scripting),
here is a sample of my sheet I need help with.
https://docs.google.com/spreadsheets/d/1iypkWdUsSkow9m8nTSFS25HgY1d5vsVMZvjEynwQJcM/edit?usp=sharing
what i need is a script that will run onedit.
the user has 5 selectable fields, highlighted in yellow. the script should run only if cell F3 is edited.
and what it should do is hide all column where row 1 does not match F3.
please please help!
I wrote this small function that hides or unhides columns depending on the value of the dropdown in F3, and that only runs when this cell is modified.
Copy this to the script bound to your spreadsheet:
function onEdit(e) {
var ss = e.source; // Spreadsheet that triggered the function
var sheet = ss.getActiveSheet();
var sheetName = "Sheet1"; // Change accordingly
var range = e.range; // Range that was edited
var editedColumn = range.getColumn();
var editedRow = range.getRow();
var column = 6;
var row = 3;
// Check that the edited cell is F3 and the edited sheet is the one you want:
if(column == editedColumn && row == editedRow && sheet.getName() == sheetName) {
var dropdownValue = range.getValue(); // Value of F3
// Index of last column with content. If you want to hide the blank columns after that, use sheet.getMaxColumns() instead:
var cols = sheet.getLastColumn();
// First row values:
var headers = sheet.getRange(1, 1, 1, cols).getValues()[0];
// Looping through each column (hiding or unhiding depending on whether value matches):
for(var i = 1; i < cols; i++) {
if(headers[i] != dropdownValue) {
sheet.hideColumns(i + 1);
} else {
sheet.showColumns(i + 1);
}
}
}
}
Please tell me if that works for you.