How to make this script run in all tabs except certain tabs (Sheets)? - sorting

I'm a total newbie when it comes to scripts and I honestly don't really use it often at all but I thought it'd be fun to automatize an alphabetical order sorting I wanted to do and so I used this script:
/** Build a menu item
From https://developers.google.com/apps-script/guides/menus#menus_for_add-ons_in_google_docs_or_sheets
**/
function onOpen(e) {
var menu = SpreadsheetApp.getUi().createMenu('Sort');
if (e && e.authMode == ScriptApp.AuthMode.NONE) {
// Add a normal menu item (works in all authorization modes).
menu.addItem('Sort Sheet', 'sort');
} else {
// Add a menu item based on properties (doesn't work in AuthMode.NONE).
var properties = PropertiesService.getDocumentProperties();
var workflowStarted = properties.getProperty('workflowStarted');
if (workflowStarted) {
menu.addItem('Sort Sheet', 'sort');
} else {
menu.addItem('Sort Sheet', 'sort');
}
menu.addToUi();
}
}
function sort() {
/** Variables for customization:
Each column to sort takes two variables:
1) the column index (i.e. column A has a colum index of 1
2) Sort Asecnding -- default is to sort ascending. Set to false to sort descending
**/
//Variable for column to sort first
var sortFirst = 3; //index of column to be sorted by; 1 = column A, 2 = column B, etc.
var sortFirstAsc = true; //Set to false to sort descending
//Variables for column to sort second
var sortSecond = 1;
var sortSecondAsc = true;
//Number of header rows
var headerRows = 1;
/** End Variables for customization**/
/** Begin sorting function **/
var activeSheet = SpreadsheetApp.getActiveSheet();
var sheetName = activeSheet.getSheetName(); //name of sheet to be sorted
var sheet = SpreadsheetApp.getActive().getSheetByName(sheetName);
var range = sheet.getRange(headerRows+1, 1, sheet.getMaxRows()-headerRows, sheet.getLastColumn());
range.sort([{column: sortFirst, ascending: sortFirstAsc}, {column: sortSecond, ascending: sortSecondAsc}]);
}
It worked very well but I wondered if there was a way to not have it work in two specific tabs of the same sheets?

I believe your goal as follows.
You want to execute the script of sort for the sheets except for the specific sheets.
In this case, how about declaring the excluded sheet names and checking the current sheet using the excluded sheet names? When this is reflected to your script, it becomes as follows.
Modified script:
In this case, please set the sheet names you want to exclude to excludeSheetNames. At sample script, when the active sheet is "Sheet1" and "Sheet2", the script below the if statement is not run.
function sort() {
var excludeSheetNames = ["Sheet1", "Sheet2"]; // <--- Added
var sortFirst = 3;
var sortFirstAsc = true;
var sortSecond = 1;
var sortSecondAsc = true;
var headerRows = 1;
var activeSheet = SpreadsheetApp.getActiveSheet();
var sheetName = activeSheet.getSheetName();
if (excludeSheetNames.includes(sheetName)) return; // <--- Added
var sheet = SpreadsheetApp.getActive().getSheetByName(sheetName);
var range = sheet.getRange(headerRows + 1, 1, sheet.getMaxRows() - headerRows, sheet.getLastColumn());
range.sort([{ column: sortFirst, ascending: sortFirstAsc }, { column: sortSecond, ascending: sortSecondAsc }]);
}
For example, if you want to run the script below the if statement for only excludeSheetNames, please modify if (excludeSheetNames.includes(sheetName)) return; to if (!excludeSheetNames.includes(sheetName)) return;.
Reference:
includes()

Related

Google sheets auto sort script modification to trigger only on specified column change

I have this auto sort script and it works great but I can't figure out how to change two things.
Instead of the script being triggered by every change in the entire sheet I'd like it to trigger when only two specific columns are edited (C and D).
SHEET_NAME = "North Tonawanda";
SORT_DATA_RANGE = "C:D";
SORT_ORDER = [
{column: 3, ascending: true}, // 3 = column number, sorting by descending order
{column: 4, ascending: true} // 1 = column number, sort by ascending order
];
function onEdit(e){
multiSortColumns();
}
function multiSortColumns(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName(SHEET_NAME);
var range = sheet.getRange(SORT_DATA_RANGE);
range.sort(SORT_ORDER);
ss.toast('Sort Completed.');
}
Answer
I would rewrite the script as follows. This makes it visually clearer and the onEdit trigger uses the SORT_ORDER variable to see the columns that trigger the function:
Code
SHEET_NAME = "North Tonawanda";
SORT_DATA_RANGE = "C:D";
SORT_ORDER = [
{ column: 3, ascending: true },
{ column: 4, ascending: true }
];
function multiSortColumns() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName(SHEET_NAME);
var range = sheet.getRange(SORT_DATA_RANGE);
range.sort(SORT_ORDER);
ss.toast('Sort Completed.');
}
function onEdit(e) {
var editedSheet = e.range.getSheet().getName()
var columnStart = e.range.columnStart
var columnEnd = e.range.columnEnd
if (SHEET_NAME == editedSheet
&& columnStart >= SORT_ORDER[0]["column"]
&& columnEnd <= SORT_ORDER[1]["column"]) {
multiSortColumns()
}
}
References:
Simple Triggers
Event Objects
I believe your goal as follows.
From your question and your script, you want to run the function of multiSortColumns() when the columns "C" and "D" of the sheet North Tonawanda are edited.
In this case, I would like to propose to achieve your goal using the event object. When your script is modified, it becomes as follows.
Modified script:
From:
function onEdit(e){
multiSortColumns();
}
To:
function onEdit(e){
const range = e.range;
if (range.getSheet().getSheetName() != SHEET_NAME || (range.columnStart != 3 && range.columnStart != 4)) return;
multiSortColumns();
}
References:
Simple Triggers
Event Objects

Google Apps Script that loops through a filter and sends an e-mail with a PDF?

I have data from a questionnaire (20K rows) that I need to share with the store managers (report) of our shops (400 shops). I managed to write a script that sends a pdf of my sheet to a list of e-mail addresses. But I'm stuck on writing the loop for the filter, since I can't get the setVisibleValues(values) function to work for FilterCriteriaBuilder. The setHiddenValues(values) function works, but I can't figure out how to combine that with the loop.
Sample of my Google Sheet
See below for my current code:
/**
* Filtersheet by location
*/
function FilterSheet() {
var spreadsheet = SpreadsheetApp.getActive().getSheetByName('Data')
spreadsheet.getRange('F1').activate();
var criteria = SpreadsheetApp.newFilterCriteria()
.setHiddenValues(['Amsterdam, Rotterdam'])
.build();
spreadsheet.getFilter().setColumnFilterCriteria(6, criteria);
};
/**
* Send pdf of currentspreadsheet
*/
function SendPdf() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var spreadsheet = SpreadsheetApp.getActive().getSheetByName('Adres');
var blob = DriveApp.getFileById(ss.getId()).getAs("application/pdf");
blob.setName(ss.getName() + ".pdf");
var startRow = 2; // First row of data to process
var numRows = 2; // Number of rows to process
// Fetch the range of cells A2:B3
var dataRange = spreadsheet.getRange(startRow, 1, numRows, 2);
// Fetch values for each row in the Range.
var data = dataRange.getValues();
for (var i in data) {
var row = data[i];
var emailAddress = row[0]; // First column
var message = 'I hearby send you the overview of your data'
var subject = 'Overview of data';
MailApp.sendEmail(emailAddress, subject, message,{
attachments:[blob]});
}
}
getValues() returns the values of all range's cells no matter if they are shown or hidden.
Use a loop and isRowHiddenByFilter(rowPosition) to reap out all the filtered values. You could use Array.prototype.push to add the values to a new array or use Array.prototype.splice to modify the array holdin the values returned by getValues()
Related
How to use in Google Sheets setValue only for range of filtered rows (getRange for not hidden cells)?
I managemed to solve the problem.
This script takes a google spreadsheet with 2 sheets,one with Data and one with a combination EmailAdresses.
It sends a filtered list (filter column F) of sheet Data to the corresponding salon (location) in sheet Emailadresses (var mode email). Additionally, it has the option to "store" the pdf's in your google drive (var mode store)
*/
function construct() {
// settings:
//var mode = "store";
var mode = "email";
// get list of all salons and email
var salonList = SpreadsheetApp.getActive().getSheetByName('EmailAdressen');
// set endvar for loop
var endRow = salonList.getLastRow();
// loop trough the rows to get the Salon name and the corresponding email
for(i=1;i<=endRow;i++){
var salonName = salonList.getRange(i,2).getValue();
var email = salonList.getRange(i,1).getValue();
// create an array with all salons that should be hidden (we cant pick which one to show, so we have to go the other way around...)
var filterArray = [];
// create array with all salons to hide
for(c=1;c<=endRow;c++){
// get value from email list, check if it is not the current selected one and if so add it to the list to filter out
salonFilterName = salonList.getRange(c,2).getValue();
if(salonFilterName != salonName) {
filterArray.push(salonFilterName);
}
} // end for c
// filter the list with the array we just created
var spreadsheet = filterList(filterArray);
if(mode == "email"){
// export to PDF
var pdf = exportToPdf(spreadsheet);
// email to email address belonging to this salon
emailToAddress(email, pdf);
} // end if
if(mode == "store"){
StorePdf(spreadsheet, salonName);
}
} // end for i
return;
}
function filterList(salonNameArray) {
// select data sheet
var spreadsheet = SpreadsheetApp.getActive().getSheetByName('Data');
// first remove all existing filters to make sure we are on a clean sheet
if(spreadsheet.getFilter()){
spreadsheet.getFilter().remove();
}
// create the filter
spreadsheet.getRange('F:F').createFilter();
// set criteria for filter with array passed from construct
var criteria = SpreadsheetApp.newFilterCriteria().setHiddenValues(salonNameArray).build();
// apply filter
spreadsheet.getFilter().setColumnFilterCriteria(6, criteria);
return spreadsheet;
}
function exportToPdf(ss) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var spreadsheet = SpreadsheetApp.getActive().getSheetByName('Data');
var blob = DriveApp.getFileById(ss.getId()).getAs("application/pdf");
blob.setName(ss.getName() + ".pdf");
return blob;
}
function StorePdf(ss, salonName) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var spreadsheet = SpreadsheetApp.getActive().getSheetByName('Data');
var blob = DriveApp.getFileById(ss.getId()).getBlob();
blob.setName(salonName + "_" + Utilities.formatDate(new Date(), "GMT+1", "ddMMyyyy")+".pdf");
DriveApp.createFile(blob);
return;
}
function emailToAddress(email, pdf) {
MailApp.sendEmail(email, 'Type here the subject', 'Type here the body',{
attachments:[pdf]});
return;
}

Set the sourceRange of Data Validation to an array of values

I'm creating a social media outreach tracker. I want to create a drop-down list of the contact name. The problem is that I have two sources of names on two different sheets.
I wrote a script that pulls the names from the two different sources and combines them to a single array.
I was hoping to set the source range as that array.
Here is my code:
function setDataValid_(range, sourceRange) {
var rule = SpreadsheetApp.newDataValidation()
.requireValueInRange(sourceRange, true)
.build();
range.setDataValidation(rule);
}
function onEdit() {
var auditionsSheet = SpreadsheetApp.getActiveSpreadsheet();
var castingDirectorsTab = auditionsSheet.getSheetByName("Casting Directors");
var contactsTab = auditionsSheet.getSheetByName("Contacts");
var socialMediaOutreachTab = auditionsSheet.getSheetByName("Social Media Outreach");
var lastRowCD = castingDirectorsTab.getLastRow();
var lastRowContacts = contactsTab.getLastRow();
var activeCell = socialMediaOutreachTab.getActiveCell();
var activeColumn = activeCell.getColumn();
// get data
var castingDirectorNameData = castingDirectorsTab.getRange(2, 1, lastRowCD, 1).getValues();
var contactNameData = contactsTab.getRange(2, 1, lastRowContacts, 1).getValues();
//get name data to a single arrays
var castingDirectorName = [];
castingDirectorNameData.forEach(function(yr) {
castingDirectorName.push(yr[0]);
});
var contactName = [];
contactNameData.forEach(function(yr) {
contactName.push(yr[0]);
});
// get rid of the empty bits in the arrays
for (var x = castingDirectorName.length-1; x > 0; x--) {
if ( castingDirectorName[x][0] === undefined ) {
castingDirectorName.splice( x, 1 )
}
}
for (var x = contactName.length-1; x > 0; x--) {
if ( contactName[x][0] === undefined ) {
contactName.splice( x, 1 )
}
}
//combine two data sources for data validation
var combinedNames = [];
combinedNames.push(castingDirectorName + contactName);
Logger.log (combinedNames);
Logger.log( typeof combinedNames);
// data validation set up and build
if (activeColumn == 1 && auditionsSheet.getName() == "Social Media Outreach") {
var range = auditionsSheet.getRange(activeCell.getRow(), activeColumn +1);
var sourceRange = combinedNames;
setDataValid_(range, sourceRange)
}
}
When I enter a date in Col A on Social Media Outreach, nothing happens in Col 2.
I was using an existing working nested data validation script I have but the sourceRange pulls from a sheet based on the value in the active cell. Here is that 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();
// data validation for Auditions Tab Projet Type to Project Details
if (aColumn == 9 && aSheet.getName() == 'Auditions') {
var range = aSheet.getRange(aCell.getRow(), aColumn + 1);
var sourceRange = SpreadsheetApp.getActiveSpreadsheet().getRangeByName('RefTables!' + aCell.getValue())
setDataValid_(range, sourceRange)
}
}
For this script when I select from the data validation drop-down, a new data validation comes up in the next col with the appropriate secondary data validation.
So the question is, can the source range be set to an array or do I need to put the names back into my sheet to reference a la the second script.
I've looked through the documentation and searched and can't find an answer. I'm relatively new to GAS and am not sure of all the inner workings of the data validation builder.

Auto sorting 2 sheets based on 1 of them

I'm running a spreadsheet which contains multiple sheets, in Sheet3 I'm inputting some data and running an auto sorting code, which sorts it ascending by column D.
Sheet3 Example | Sheet1 Example
The "name" and "location" in Sheet1 are imported from Sheet3 so they swap position when Sheet3 does the sorting, however, the problem is that the info from D to F (Sheet1) isn't swapping and it will display for wrong people.
This is the script I'm using:
Modified it slightly to work for a specific sheet, since I didn't need to auto sort the whole document at the time.
/*
* #author Mike Branski (#mikebranski)
* #link https://gist.github.com/mikebranski/285b60aa5ec3da8638e5
*/
var SORT_COLUMN_INDEX = 4;
var ASCENDING = true;
var NUMBER_OF_HEADER_ROWS = 2;
var SHEET_NAME = 'Sheet3';
var activeSheet;
function autoSort(sheet) {
var s = SpreadsheetApp.getActiveSheet();
if (s.getName() == SHEET_NAME) {
var range = sheet.getDataRange();
if (NUMBER_OF_HEADER_ROWS > 0) {
range = range.offset(NUMBER_OF_HEADER_ROWS, 0, (range.getNumRows() - NUMBER_OF_HEADER_ROWS));
}
range.sort( {
column: SORT_COLUMN_INDEX,
ascending: ASCENDING
} );
}
}
function onEdit(event) {
var s = SpreadsheetApp.getActiveSheet();
if (s.getName() == SHEET_NAME) {
var editedCell;
activeSheet = SpreadsheetApp.getActiveSheet();
editedCell = activeSheet.getActiveCell();
if (editedCell.getColumn() == SORT_COLUMN_INDEX) {
autoSort(activeSheet);
}
}
}
function onOpen(event) {
var s = SpreadsheetApp.getActiveSheet();
if (s.getName() == SHEET_NAME) {
activeSheet = SpreadsheetApp.getActiveSheet();
autoSort(activeSheet);
}
}
function onInstall(event) {
onOpen(event);
}
So basically when I edit Sheet3 and it does the auto sorting, I want the rows from D to F in Sheet1 to carry along with repositioning that comes from Sheet3. I hope I did manage to explain properly what I want.
I've tried without success to make it work; I can't figure out the proper way of doing this, especially due to the fact that Sheet1 table has different range.
I figured out how to fix the issue so I'll post the code here. Basically whenever you edit the column that you choose to sort by in Sheet3 (master sheet) it will first copy in the Sheet1 (target sheet) what changes you've made in A & B columns and then it will sort both sheets at the same time, this way the data from following columns in Sheet1 will carry along.
I used A & B columns in this example, since that's what I commented above, but can be different ranges as long as they're similar in size.
// Master Sheet Settings (Copy ranges must be similar in size)
var msName = 'Master Sheet';
var msSortCol = 4; // which column to trigger the sorting when you edit
var msSkipRows = 6; // how many rows to skip, if you have header rows
var msCopyRange = 'A7:B51'; // the range you want to copy
// Target Sheet Settings
var tsSortCol = 3;
var tsSkipRows = 10;
var tsName = 'Target Sheet';
var tsCopyRange = 'A11:B55';
var sortAscending = true;
var activeSheet;
function onEdit() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var editedCell = ss.getActiveRange().getColumnIndex();
if (ss.getSheetName() == msName) {
activeSheet = SpreadsheetApp.getActiveSheet();
if (editedCell == msSortCol) {
copyRow();
autoSort(activeSheet);
}
}
}
function copyRow() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName(msName);
var values = sheet.getRange(msCopyRange).getValues();
ss.getSheetByName(tsName).getRange(tsCopyRange).setValues(values);
SpreadsheetApp.flush();
}
function autoSort() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var msheet = ss.getSheetByName(msName);
var tsheet = ss.getSheetByName(tsName);
var mrange = msheet.getDataRange();
var trange = tsheet.getDataRange();
if (ss.getSheetName() == msName) {
if (msSkipRows > 0) {
mrange = mrange.offset(msSkipRows, 0, (mrange.getNumRows() - msSkipRows));
}
if (tsSkipRows > 0) {
trange = trange.offset(tsSkipRows, 0, (trange.getNumRows() - tsSkipRows));
}
mrange.sort({ column: msSortCol, ascending: sortAscending });
trange.sort({ column: tsSortCol, ascending: sortAscending });
}
}

Enable data filters on a range with Google Apps Script

Is there a way to enable data filters by script in a Google Sheet for a certain range? Currently this can be doen manually, but I do not want to select a range, then click 'Data', and then turn on filter.
I know that the filter will remain in an existing sheet. However, I try to apply a filter for a new spreadsheet that is generated via scripting.
My idea is:
function foo() {
var spreadsheet = SpreadsheetApp.getActive();
var infoSheet = spreadsheet.insertSheet('sheetName', spreadsheet.getNumSheets());
infoSheet.getRange(1, 1, 5, 5). -> enable filter?
...
}
How can I achieve my goal programmatically?
Filters are now also available in the native Spreadsheet Service, without needing to activate the Sheets REST API via "Advanced Services".
The above Sheets REST API method, adapted for native Apps Script:
function applyFilter() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var dataSheet = ss.getActiveSheet();
var toFilter = dataSheet.getDataRange();
var filter = toFilter.createFilter();
// Make some criteria to filter with.
var fcb = SpreadsheetApp.newFilterCriteria();
/* use FilterCriteria methods */
fcb.whenCellNotEmpty();
// Filter the range based on the 1st column:
filter.setColumnFilterCriteria(1, fcb.build());
}
Further reading:
Filter Reference
FilterCriteria Reference
Now you can apply filters using google sheets advanced service.
First you need to turn on the Google Sheets API. For that follow the steps as mentioned in the below link:
https://developers.google.com/sheets/api/quickstart/apps-script
After that you can use the following function to apply filters.
function applyFilter() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var ssId = ss.getId();
var dataSheet = ss.getActiveSheet();
var lastRow = dataSheet.getLastRow();
var lastColumn = dataSheet.getLastColumn();
var sheetId = dataSheet.getSheetId();
var filterSettings = {
"range": {
"sheetId": sheetId,
"startRowIndex": 0,
"endRowIndex": lastRow,
"startColumnIndex": 0,
"endColumnIndex": lastColumn
}
};
var requests = [{
"setBasicFilter": {
"filter": filterSettings
}
}];
Sheets.Spreadsheets.batchUpdate({'requests': requests}, ssId);
}
A short answer would be "No".
It seems that GAS supports neither getting, nor setting Filter criteria for columns, at least according to this open issue.
An alternative solution would be to do the filtering and sorting on GAS side, and push the distinct results into different columns or sheets.
Please use this script if you want:
Create a new filter
Create a new filter based on the existing filter and preserve criteria
Create a filter with default bounds: row 1, column 1, last row, last column
The Script
/**
* Creates/Updates filter
* Preserves previously filtered data
*
* Parameters are optional
*
* #param {string} sheet (active)
* #param {integer} row1 (1)
* #param {integer} row2 (last)
* #param {integer} column1 (1)
* #param {integer} column2 (last)
*/
function createFilter_(sets) {
// sheet
var ss = SpreadsheetApp.getActive();
var s;
if (sets.sheet) {
s = ss.getSheetByName(sets.sheet);
} else {
s = ss.getActiveSheet();
}
// existing filter?
var filter = s.getFilter();
// finding range boundaries 4 filter
var columns = [], criterias = [], criteria;
var column1, column2, row1;
var row2 = sets.row2 || s.getMaxRows();
if (filter) {
// remember criterias
var r = filter.getRange();
row1 = r.getRow();
if (!sets.row1 || row1 === sets.row1) {
// use old filter boundaries
column1 = r.getColumn();
column2 = r.getWidth() + column1 - 1;
for (var i = column1; i <= column2; i++) {
columns.push(i);
criteria = filter.getColumnFilterCriteria(i);
criterias.push(criteria);
}
} else {
row1 = sets.row1
column1 = sets.column1 || 1;
column2 = sets.column2 || s.getMaxColumns();
}
// remove filter
filter.remove();
} else {
// create new filter
column1 = sets.column1 || 1;
column2 = sets.column2 || s.getMaxColumns();
row1 = sets.row1 || 1;
}
// range
var range = s.getRange(
row1,
column1,
row2 - row1 + 1,
column2 - column1 + 1
);
// create filter
var newfilter = range.createFilter();
// adjust criterias if needed
for (var i = 0; i < columns.length; i++) {
if (criterias[i]) {
newfilter.setColumnFilterCriteria(
columns[I],
criterias[I]);
}
}
return '✔️ filter for range ' + range.getA1Notation();
}
Usage
function createNewFilter() {
var sets = {
sheet: 'Sheet2',
row1: 5,
row2: 50,
column1: 2,
column2: 8
}
var result = createFilter_(sets);
console.log(result);
}
Update existing filter:
function updateExistingFilter() {
var sets = {
sheet: 'Sheet1'
}
var result = createFilter_(sets);
console.log(result);
}

Resources