Google Sheets Auto Filter - google-apps-script

I am trying to make an automated google sheet which will filter pending IDs on column P every 5 minutes to avoid reediting the entry.
So I tried to record a macro and put a trigger on it but the trigger didn't work. I am having an error of "setColumnFilterCriteria' of null at UntitledMacro(macros:7:43)"
The editors of the said file is about 10 people with editor restriction but are retricted on other columns. Hope you can help. Thanks in advance
Below were the codes of the macro recorder:
function UntitledMacro() {
var spreadsheet =
SpreadsheetApp.getActive();
spreadsheet.Getrange('P1').activate(); varcriteria=SpreadsheetApp.newFilterCriteria().setHiddenValues(['Completed', 'Other']).build(); spreadsheet.getActiveSheet().getFilter().setColumnFilterCriteria(16, criteria) ;
};

You encountered such error because your current active sheet doesn't have an existing filter. If there is no current filter in the sheet, getFilter() will return null. Hence setColumnFilterCriteria() don't exist.
As a workaround, you can include an if condition to check if the current sheet doesn't have any filter yet. Then create a new filter.
Sample Code:
function UntitledMacro() {
var spreadsheet = SpreadsheetApp.getActive();
Logger.log(spreadsheet.getActiveSheet().getFilter());
spreadsheet.getRange('P1').activate();
var criteria = SpreadsheetApp.newFilterCriteria().setHiddenValues(['Completed', 'Other']).build();
var filter = spreadsheet.getActiveSheet().getFilter();
if(filter){
filter.setColumnFilterCriteria(16, criteria);
}else{
//There is currently no filter in the selected sheet
//Create new filter in Cell P1
spreadsheet.getActiveSheet().getRange('P1:P').createFilter().setColumnFilterCriteria(16, criteria);
}
};

Related

Google Apps script on cell background change recalculation [duplicate]

I know this question has been asked before but the answers given are not valid for my case because it's slightly different.
I've created a formula that looks for sheets with a pattern in the name and then uses it's content to generate the output. For example
function sampleFormula(searchTerm) {
const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
const sheets = spreadsheet.getSheets()
.filter(function(sheet) {
// If sheet name starts with DATA_SHEET_...
return sheet.getSheetName().indexOf('DATA_SHEET_') === 0;
});
const result = [];
sheets.forEach(function(sheet) {
// We get all the rows in the sheet
const rows = sheet.getDataRange().getValues();
rows.forEach(function(row) => {
// If the row it's what we are looking for we pick the columns A and C
if (row[1] === searchTerm) {
result.push([ row[0], row[2] ])
}
});
});
// If we found values we return them, otherwise we return emptry string
return result.length ? result : '';
}
The thing is I need this formula to be re-calculated when a cell in a sheet with a name starting with DATA_SHEET_ changes.
I see most answers (and what I usually do) is to pass the range we want to watch as a parameter for the formula even if it's not used. But in this case it will not work because we don't know how many ranges are we watching and we don't even know the whole sheet name (it's injected by a web service using Google Spreadsheet API).
I was expecting Google Script to have something like range.watch(formula) or range.onChange(this) but I can't found anything like that.
I also tried to create a simple function that changes the value of cell B2 which every formula depends on but I need to restore it immediately so it's not considered a change (If I actually change it all formulas will break):
// This does NOT work
function forceUpdate() {
const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
const sheet = spreadsheet.getActiveSheet();
const range = sheet.getRange(1, 1);
const content = range.getValue();
range.setValue('POTATO');
range.setValue(content);
}
So I don't know what else can I do, I have like a hundred formulas on multiple sheets doing this and they are not updating when I modify the DATA_SHEET_... sheets.
To force that a custom function be recalculated we could use a "triggering argument" that it's only taks will be to trigger the custom function recalculation. This triggering argument could be a cell reference that will be updated by a simple edit trigger or we could use an edit installable trigger to update all the formulas.
Example of using a cell reference as triggering argument
=sampleFormula("searchTerm",Triggers!A1)
Example of using an edit installable trigger to update all the formulas
Let say that formulas has the following form and the cell that holds the formula is Test!A1 and Test!F5
=sampleFormula("searchTerm",0)
where 0 just will be ignored by sampleFormula but will make it to be recalculated.
Set a edit installable trigger to fire the following function
function forceRecalculation(){
updateFormula(['Test!A1','Test!F5']);
}
The function that will make the update could be something like the following:
function updateFormula(references){
var rL = SpreadsheetApp.getActive().getRangeList(references);
rL.getRanges().forEach(function(r){
var formula = r.getFormula();
var x = formula.match(/,(\d+)\)/)[1];
var y = parseInt(x)+1;
var newFormula = formula.replace(x,y.toString());
r.setFormula(newFormula);
});
}
As you can imagine the above example will be slower that using a cell reference as the triggering argument but in some scenarios could be convenient.

Run a script every time there is a change in a cell

I want to run this sript every time there is a change in notes!C4 and automatically copy the value to notes!D4 cell
function Copy() {
var ss = SpreadsheetApp.getActive().getSheetByName("notes") ;
ss.getRange('notes!C4').copyTo(ss.getRange('notes!D4'), SpreadsheetApp.CopyPasteType.PASTE_VALUES, false);
}
Update 1
If this it can't be done, I have in another spreadsheet a script that copies the link http://openinsider.com/screener?s= every time I run it to L2 cell of that sheet.
I'm taking that value with an =ImportRange to the actual spreadsheet. How can I copy that link to notes!D4 that's in another spreadsheet to the actual one?
function Refresh() {
var ss = SpreadsheetApp.getActive().getSheetByName("sheet1") ;
var cell = ss.getRange("L2");
cell.clearContent();
SpreadsheetApp.flush();
Utilities.sleep(5000); // You have 5 second to check that the cell has cleared
cell.setValue('http://openinsider.com/screener?s=');
}
Update 2
I've tried this, the first time the script was charging but it didn't make anything. I'm new to google scripts and I don't know how to make it working.
function onEdit(e) {
if (e.range.getA1Notation() === 'C4') {
SpreadsheetApp.getActiveSpreadsheet().getSheetByName("notes").getRange('D4').CopyPasteType.PASTE_VALUES;
}
}
As mentioned by #Cooper, you just need to use onEdit(e) Simple Trigger to run your script whenever there is a cell being modified.
I will just refer on your latest update, you can refer to this sample code:
function onEdit(e) {
var ss = e.source;
var cell = e.range;
if(cell.getA1Notation() === "C4" && ss.getActiveSheet().getName() == "notes"){
ss.getActiveSheet().getRange("D4").setValue(e.value);
}
}
What it does?
Get the spreadsheet object using Google Sheets events source parameter
Get the range object using Google Sheets events range parameter
Check if the modified cell is in sheet notes and in cell C4, I get the sheet object using Spreadsheet.getActiveSheet() method and get its sheet name using Sheet.getName(). This will make sure that your function will only run when Sheet notes!C4 was modified.
Set the value of the cell D4 using Range.setValue(value). To maximize the event object, I used value parameter in Google Sheets events.
Additional Tips:
Please be mindful of the methods available in each classes/objects that you are using. For example in this code:
SpreadsheetApp.getActiveSpreadsheet().getSheetByName("notes").getRange('D4').CopyPasteType.PASTE_VALUES;
You get the range object of cell D4 using Sheet.getRange(a1Notation)
range object doesn't have CopyPasteType.PASTE_VALUES in its methods

Force Google Spreadsheet formula to recalculate

I know this question has been asked before but the answers given are not valid for my case because it's slightly different.
I've created a formula that looks for sheets with a pattern in the name and then uses it's content to generate the output. For example
function sampleFormula(searchTerm) {
const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
const sheets = spreadsheet.getSheets()
.filter(function(sheet) {
// If sheet name starts with DATA_SHEET_...
return sheet.getSheetName().indexOf('DATA_SHEET_') === 0;
});
const result = [];
sheets.forEach(function(sheet) {
// We get all the rows in the sheet
const rows = sheet.getDataRange().getValues();
rows.forEach(function(row) => {
// If the row it's what we are looking for we pick the columns A and C
if (row[1] === searchTerm) {
result.push([ row[0], row[2] ])
}
});
});
// If we found values we return them, otherwise we return emptry string
return result.length ? result : '';
}
The thing is I need this formula to be re-calculated when a cell in a sheet with a name starting with DATA_SHEET_ changes.
I see most answers (and what I usually do) is to pass the range we want to watch as a parameter for the formula even if it's not used. But in this case it will not work because we don't know how many ranges are we watching and we don't even know the whole sheet name (it's injected by a web service using Google Spreadsheet API).
I was expecting Google Script to have something like range.watch(formula) or range.onChange(this) but I can't found anything like that.
I also tried to create a simple function that changes the value of cell B2 which every formula depends on but I need to restore it immediately so it's not considered a change (If I actually change it all formulas will break):
// This does NOT work
function forceUpdate() {
const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
const sheet = spreadsheet.getActiveSheet();
const range = sheet.getRange(1, 1);
const content = range.getValue();
range.setValue('POTATO');
range.setValue(content);
}
So I don't know what else can I do, I have like a hundred formulas on multiple sheets doing this and they are not updating when I modify the DATA_SHEET_... sheets.
To force that a custom function be recalculated we could use a "triggering argument" that it's only taks will be to trigger the custom function recalculation. This triggering argument could be a cell reference that will be updated by a simple edit trigger or we could use an edit installable trigger to update all the formulas.
Example of using a cell reference as triggering argument
=sampleFormula("searchTerm",Triggers!A1)
Example of using an edit installable trigger to update all the formulas
Let say that formulas has the following form and the cell that holds the formula is Test!A1 and Test!F5
=sampleFormula("searchTerm",0)
where 0 just will be ignored by sampleFormula but will make it to be recalculated.
Set a edit installable trigger to fire the following function
function forceRecalculation(){
updateFormula(['Test!A1','Test!F5']);
}
The function that will make the update could be something like the following:
function updateFormula(references){
var rL = SpreadsheetApp.getActive().getRangeList(references);
rL.getRanges().forEach(function(r){
var formula = r.getFormula();
var x = formula.match(/,(\d+)\)/)[1];
var y = parseInt(x)+1;
var newFormula = formula.replace(x,y.toString());
r.setFormula(newFormula);
});
}
As you can imagine the above example will be slower that using a cell reference as the triggering argument but in some scenarios could be convenient.

set newly created google sheet as the active sheet

I am using google sheets to import data into a mysql database. Each month a new sheet gets created for instance 06_2017. Is there a way to set the newly created sheet as active sheet or based on current month & year set that sheet as active. Instead of having to hard code
var gsheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('06_2017');
var gsheetname = sheet.getSheetName();
var gdata = sheet.getDataRange().getValues();
As #Cooper have said, you can store the name of the newly created sheet in UserProperties, then place your code in onOpen() method of your script.
You can do it like this,
function onOpen(e)
{
var prop = PropertiesService.getDocumentProperties();
var sheet_name = prop.getProperty(<NAME_OF_SHEET>);
var sp = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheet_name).activate();
}
If you are going to stick to the pattern 'MM_YYYY' for naming your sheets, the following code will always activate the sheet with the latest date. If your plan is to use different naming patterns in a single document, you need to refactor the code below to allow for this.
The onOpen() function is one of the so-called simple triggers in GAS, so every code inside this function will be executed when you open the document. The sort() method will sort array elements based on given criteria. Since sort() doesn't know what criteria you'd like to use for sorting Sheet objects, you need to pass a comparator function to override the default one.
The comparator function parses sheet names and creates date objects by passing year and date (in that order) to the new Date() constructor. It then uses the result of date subtraction to sort the 'sheets' array in descending order, from the highest to the lowest value.
Finally, you take the first element of the sorted array (that would be the latest created sheet) and set it as the active sheet.
function onOpen() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheets = ss.getSheets();
sheets.sort(function(a,b) {
var arr1 = a.getName().split("_");
var arr2 = b.getName().split("_");
return new Date(arr2[1], arr2[0]) - new Date(arr1[1], arr1[0]);
});
if (sheets.length < 1) {
return;
}
ss.setActiveSheet(sheets[0]);
}

Google Sheets Create new tab

Need some advise on Google Sheets.
Sadly one of our clients uses Google Sheets as their Excel fix. As part of this one of the staff spends hours moving data about.
The first part of the problem is that we have a tab called master. In column A is a variable amount of cells (Some duplicates). We want to be able to create a new tab based on the distinct value in the cell (So one sheet per distinct value)
Now in Microsoft Excel VBA I can write this with my eyes closed, but on Google sheets I have no idea.
Any help is appreciated.
How about:
function createNewSheets() {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var masterSheet = spreadsheet.getSheetByName('master');
// Retrieve 2d array for column A
var colA = masterSheet.getRange('A:A').getValues();
// Create a 1d array of unique values
var uniqueValues = {};
colA.forEach(function(row) {
row[0] ? uniqueValues[row[0]] = true : null;
});
var newSheetNames = Object.keys(uniqueValues);
newSheetNames.forEach(function(sheetName) {
// Check to see whether the sheet already exists
var sheet = spreadsheet.getSheetByName(sheetName);
if (!sheet) {
spreadsheet.insertSheet(sheetName);
}
});
}