Let's say I am getting the range A2:B2:
sheet.getRange("A2:B2").getValues();
But I added a row under A1:B1, so now my values are in A3:B3
Is it possible for Apps Script to dynamically catch that my values are now on another range ?
If not, any alternative ideas on how I can dynamically get the range of moving rows ?
I believe your goal is as follows.
You want to know whether the values of a range of "A2:B2" is moved.
In this case, how about using the named range, OnChange trigger, and PropertiesService? I thought that when those are used, your goal might be able to be achieved. When the sample script is prepared, it becomes as follows.
Usage:
1. Create a named range.
As a sample, please create a named range to the cells "A2:B2" as "sampleNamedRange1". Ref
2. Prepare sample script.
Please copy and paste the following script to the script editor of Spreadsheet. And, please install OnChange trigger to the function installedOnChange.
function installedOnChange(e) {
var originalRange = "A2:B2";
var nameOfNamedRange = "sampleNamedRange1";
if (!["INSERT_ROW", "REMOVE_ROW"].includes(e.changeType)) return;
var p = PropertiesService.getScriptProperties();
var pv = p.getProperty("range");
if (!pv) pv = originalRange;
var range = e.source.getRangeByName(nameOfNamedRange);
var a1Notation = range.getA1Notation();
if (a1Notation != pv) {
p.setProperty("range", a1Notation);
Browser.msgBox(`Range was changed from ${pv} to "${a1Notation}". Original range is "${originalRange}".`);
}
// var values = range.getValues(); // The values are not changed because of the named range.
}
3. Testing.
In this case, please do I added a row under A1:B1. By this, the script is automatically run by the OnChange trigger. And, you can see the dialog. You can see the demonstration as shown in the following image.
Note:
This is a simple sample script. So, please modify this for your actual situation.
References:
Installable Triggers
getRangeByName(name)
Properties Service
Added:
From the discussions, I understood that you wanted to retrieve the named range of the specific sheet using the name of the named range. In this case, the sample script is as follows.
When the sheet name is used
const range = SpreadsheetApp.getActive().getSheetByName("sheetName").getRange("nameOfNamedRange");
or
const range = SpreadsheetApp.getActive().getSheetByName("sheetName").getNamedRanges().find(n => n.getName() == "nameOfNamedRange").getRange();
When the active sheet is used
const range = SpreadsheetApp.getActiveSheet().getRange("nameOfNamedRange");
or
const range = SpreadsheetApp.getActiveSheet().getNamedRanges().find(n => n.getName() == "nameOfNamedRange").getRange();
This is possible through DeveloperMetadata. Metadata can be set to ranges or sheets and whenever such data(ranges or sheets) are moved, the associated metadata moves along with it as well. Unfortunately, this metadata cannot be set to arbitrary ranges, but only to single column or single row. For eg, with A2:B2, We have to set the metadata to the entirety of column A, column B and Row 2. However, once set, apps script is no more needed. Google sheets automatically keeps track of the movements of such data.
Sample script:
const setDevMetadata_ = (sheetName = 'Sheet1', rng = '2:2', key = 'a2b2') => {
SpreadsheetApp.getActive()
.getSheetByName(sheetName)
.getRange(rng)
.addDeveloperMetadata(key);
};
/**
* #description Set metadata to a specific range
* Unfortunately, this metadata cannot be set to arbitrary ranges, but only to single column or single row.
* For eg, with `A2:B2`, We have to set the metadata to the entirety of column A, column B and Row 2.
* #see https://stackoverflow.com/a/73376887
*/
const setDevMetadataToA2B2 = () => {
['2:2', 'A:A', 'B:B'].forEach((rng) => setDevMetadata_(undefined, rng));
};
/**
* #description Get a range with specific developer metadata key
*/
const getRangeWithKey = (sheetName = 'Sheet1', key = 'a2b2') => {
const sheet = SpreadsheetApp.getActive().getSheetByName(sheetName),
devFinder = sheet.createDeveloperMetadataFinder(),
[rows, columns] = ['Row', 'Column'].map((rc) =>
devFinder
.withKey(key)
.withLocationType(
SpreadsheetApp.DeveloperMetadataLocationType[rc.toUpperCase()]
)
.find()
.map((devmetadata) =>
devmetadata.getLocation()[`get${rc}`]()[`get${rc}`]()
)
);
console.log({ rows, columns });
const rng = sheet.getRange(
rows[0],
columns[0],
rows[1] ? rows[1] - rows[0] + 1 : 1,
columns[1] ? columns[1] - columns[0] + 1 : 1
);
console.log(rng.getA1Notation());
return rng;
};
Related
I have two sheets. In one, the data of the manufactured devices is already entered. In the second, new warranty claims are added daily. To shorten the working time, I automatically copy the production data from the sheet Production to sheet Complaints. Currently I am solving this with VLOOKUP or INDEX function. But it works very slow when number of rows >20k.
Is it possible to write a script (using AppScripts) where after entering the ID number in the complaint sheet and selecting this cell, script will independently fill in the appropriate columns with data from 'Production'? (the order of the columns is different in both sheet)
Link to my sheet with example
I've tried all option with built-in function, I'm expecting to know and understand a possible solution using AppScript
In your situation, how about the following sample script?
Sample script:
Please copy and paste the following script to the script editor of Spreadsheet and save the script. And please confirm the sheet names again.
function myFunction() {
// Retrieve sheets.
const ss = SpreadsheetApp.getActiveSpreadsheet();
const productionSheet = ss.getSheetByName("Production");
const complaintsSheet = ss.getSheetByName("Complaints");
// Retrieve values from sheets.
const [h1, ...v1] = productionSheet.getDataRange().getValues();
const range = complaintsSheet.getRange("A2:I" + complaintsSheet.getLastRow());
const [h2, ...v2] = range.getValues();
// Create an array for putting to Complaints sheet by converting columns.
const hconv = h2.map(h => h1.indexOf(h));
const obj = v1.reduce((o, r) => (o[r[0]] = hconv.map(i => i > -1 ? r[i] : null), o), {});
const len = Object.values(obj)[0].length;
const res = v2.map(([a]) => obj[a] || Array(len).fill(null));
// Put the created array to Complaints sheet.
range.offset(1, 0, res.length, res[0].length).setValues(res);
}
When this script is run, the values are retrieved from both sheets. And, an array is created for putting to "Complaints" sheet by checking the ID of column "A" and converting the columns. And, the array is put to "Complaints" sheet.
Note:
In this sample, I tested it using your sample Spreadsheet. If you change the spreadsheet, this script might not be able to be used. Please be careful about this.
I thought that in this case, the custom function might be able to be used. But, from your question, if the data is large, the custom function cannot be used. So, I proposed the above script.
In this sample, in order to convert the columns, the header titles are used. So, if you change the header titles, please be careful about this.
As another approach, if you want to put the value to the row when you put a value to the column "A" of "Complaints", how about the following script? In this script, the script is automatically run by a simple trigger of OnEdit. So, in this case, when you put a value to the column "A" of "Complaints" sheet, this script is automatically run, and the value is put into the edited row. Please select one of 2 scripts for your actual situation. As an important point, when you use this, please don't directly run this function because of no event object. Please be careful about this.
function onEdit(e) {
const { range, source } = e;
const complaintsSheet = range.getSheet();
if (complaintsSheet.getSheetName() != "Complaints" || range.columnStart != 1 || range.rowStart <= 2) return;
const value = range.getValue();
const productionSheet = source.getSheetByName("Production");
const [h1, ...v1] = productionSheet.getDataRange().getValues();
const [h2] = complaintsSheet.getRange("A2:I2").getValues();
const hconv = h2.map(h => h1.indexOf(h));
const obj = v1.reduce((o, r) => (o[r[0]] = hconv.map(i => i > -1 ? r[i] : null), o), {});
const len = Object.values(obj)[0].length;
const res = [obj[value] || Array(len).fill(null)];
range.offset(0, 0, res.length, res[0].length).setValues(res);
}
References:
reduce()
map()
I have a budget spreadsheet with tabs for every pay period. These tabs are created as needed and don't have names I can easily know in advance. For instance, one will be "10/15 - 10/28" because that's the pay period. Next month I create a new one with "10/29 - 11/11." I'd like to be able to sum a value across all sheets. For example, every sheet has a row named "Save," some sheets have a row named "Rent", but not every sheet will contain rows with those names and when they do they won't always be in the same cell number.
Sample sheet
I've seen some examples where there's a bunch of SUMIFs and every sheet is manually named but I'd much rather not have to do that because this sheet gets copied fairly often and the sheet names will never be the same.
=SUMIFS('Tab 1' !A1:A10, 'Tab 1'!B1:B10, "Rent")
+SUMIFS('Tab 2' !A1:A10, 'Tab 2'!B1:B10, "Rent")
+SUMIFS('Tab 3' !A1:A10, 'Tab 3'!B1:B10, "Rent")
Is this possible with either a standard formula or a script?
Sample Data
Desired final tab
Column 1's values are known in advance so those can be hardcoded. For instance, there will never be a random "yet more stuff" appear which I wouldn't sum up by adding a new row to the final tab.
While there's another answer that works for this, I think the use of text finders and getRange, getValue and setFormula in loops is not the best approach, since it greatly increases the amount of calls to the spreadsheet service, slowing down the script (see Minimize calls to other services).
Method 1. onEdit trigger:
An option would be to use an onEdit trigger to do the following whenever a user edits the spreadsheet:
Loop through all sheets (excluding Totals).
For each sheet, loop through all data.
For each row, check if the category has been found previously.
If it has not been found, add it (and the corresponding amount) to an array storing the totals (called items in the function below).
If it has been found, add the current amount to the previous total.
Write the resulting data to Totals.
It could be something like this (check inline comments for more details):
const TOTAL_SHEET_NAME = "Totals";
const FIRST_ROW = 4;
function onEdit(e) {
const ss = e.source;
const targetSheet = ss.getSheetByName(TOTAL_SHEET_NAME);
const sourceSheets = ss.getSheets().filter(sheet => sheet.getName() !== TOTAL_SHEET_NAME);
let items = [["Category", "Amount"]];
sourceSheets.forEach(sheet => { // Loop through all source sheets
const values = sheet.getRange(FIRST_ROW, 1, sheet.getLastRow()-FIRST_ROW+1, 2).getValues();
values.forEach(row => { // Loop through data in a sheet
const [category, amount] = row;
const item = items.find(item => item[0] === category); // Find category
if (!item) { // If category doesn't exist, create it
items.push([category, amount]);
} else { // If category exists, update the amount
item[1] += amount;
}
});
});
targetSheet.getRange(FIRST_ROW-1, 1, items.length, items[0].length).setValues(items);
}
Method 2. Custom function:
Another option would be to use an Apps Script Custom Function.
In this case, writing the data via setValues is not necessary, returning the results would be enough:
const TOTAL_SHEET_NAME = "Totals";
const FIRST_ROW = 4;
function CALCULATE_TOTALS() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sourceSheets = ss.getSheets().filter(sheet => sheet.getName() !== TOTAL_SHEET_NAME);
let items = [["Category", "Amount"]];
sourceSheets.forEach(sheet => { // Loop through all source sheets
const values = sheet.getRange(FIRST_ROW, 1, sheet.getLastRow()-FIRST_ROW+1, 2).getValues();
values.forEach(row => { // Loop through data in a sheet
const [category, amount] = row;
const item = items.find(item => item[0] === category); // Find category
if (!item) { // If category doesn't exist, create it
items.push([category, amount]);
} else { // If category exists, update the amount
item[1] += amount;
}
});
});
return items;
}
Once the script is saved, you can use this function the same you would use any sheets built-in function:
The problem with this approach is that the formula won't recalculate automatically when changing any of the source data. In order to do that, see the above method.
Method 3. onSelectionChange trigger:
From your comment:
I'd love to be able to trigger it when the totals sheet is opened but that doesn't appear to be possible
You can do this by using an onSelectionChange trigger in combination with PropertiesService.
The idea would be that, every time a user changes cell selection, the function should check whether current sheet is Totals and whether the previously active sheet is not Totals. If that's the case, this means the user just opened the Totals sheet, and the results should update.
It could be something like this:
function onSelectionChange(e) {
const range = e.range;
const sheet = range.getSheet();
const sheetName = sheet.getName();
const previousSheetName = PropertiesService.getUserProperties().getProperty("PREVIOUS_SHEET");
if (sheetName === TOTAL_SHEET_NAME && previousSheetName !== TOTAL_SHEET_NAME) {
updateTotals(e);
}
PropertiesService.getUserProperties().setProperty("PREVIOUS_SHEET", sheetName);
}
function updateTotals(e) {
const ss = e.source;
const targetSheet = ss.getSheetByName(TOTAL_SHEET_NAME);
const sourceSheets = ss.getSheets().filter(sheet => sheet.getName() !== TOTAL_SHEET_NAME);
let items = [["Category", "Amount"]];
sourceSheets.forEach(sheet => { // Loop through all source sheets
const values = sheet.getRange(FIRST_ROW, 1, sheet.getLastRow()-FIRST_ROW+1, 2).getValues();
values.forEach(row => { // Loop through data in a sheet
const [category, amount] = row;
const item = items.find(item => item[0] === category); // Find category
if (!item) { // If category doesn't exist, create it
items.push([category, amount]);
} else { // If category exists, update the amount
item[1] += amount;
}
});
});
targetSheet.getRange(FIRST_ROW-1, 1, items.length, items[0].length).setValues(items);
}
Note: Please notice that, in order for this trigger to work, you need to refresh the spreadsheet once the trigger is added and every time the spreadsheet is opened (ref).
Reference:
onEdit(e)
Custom Functions in Google Sheets
onSelectionChange(e)
I wrote 2 scripts:
budgetTotal which takes a budgetCategory parameter, for example "Rent", and loops through all the sheets in the file to sum up the amounts listed on each sheet for that category.
budgetCreation which looks at your Totals sheet and writes these budgetTotal formulas in for each category you have listed.
I ran into a challenge which was, as I added new sheets the formulas wouldn't be aware and update the totals. So, what I did was create a simple button that executes the budgetCreation script. This way, as you add new payroll weeks you just need to press the button and - voila! - the totals update.
There might be a better way to do this using onEdit or onChange triggers but this felt like a decent starting place.
Here's a copy of the sheet with the button in place.
const ws=SpreadsheetApp.getActiveSpreadsheet()
const ss=ws.getActiveSheet()
const totals=ws.getSheetByName("Totals")
function budgetCreation(){
var budgetStart = totals.createTextFinder("Category").findNext()
var budgetStartRow = budgetStart.getRow()+1
var budgetEndRow = ss.getRange(budgetStart.getA1Notation()).getDataRegion().getLastRow()
var budgetCategoies = budgetEndRow - budgetStartRow + 1
ss.getRange(budgetStartRow,2,budgetCategoies,1).clear()
for (i=0; i<budgetCategoies; i++){
var budCat = ss.getRange(budgetStartRow+i,1).getValue()
var budFormula = `=budgetTotal(\"${budCat}\")`
ss.getRange(budgetStartRow+i,2).setFormula(budFormula)
}
}
function budgetTotal(budgetCategory) {
var sheets = ws.getSheets()
var total = 0
for (i=0; i<sheets.length; i++){
if (sheets[i].getName() != totals.getName()){
var totalFinder = sheets[i].createTextFinder(budgetCategory).findNext()
if (totalFinder == null){
total = 0
} else {
var totalValueFinder = sheets[i].getRange(totalFinder.getRow(),totalFinder.getColumn()+1).getValue()
total += totalValueFinder
}
}
}
return total
}
I have bill of materials report where the data is double stacked when exported from database.
Original data:
Sheet is very large, but I want to format the data like this
Still a noob with appscript, but how would i script this to do that?
Thanks in advance.
I believe your goal as follows.
You want to achieve the following conversion on Google Spreadsheet using Google Apps Script.
From: Source sheet
To: Destination sheet
Flow:
In this case, I would like to propose the following flow of the sample script.
Retrieve values from the source sheet.
Create values for putting to the destination sheet an 2 dimensional array.
In this case, in the following sample script, the value of column "A" is checked. When the value of column "A" is not empty, the values of columns "A" to "D" is set to temp array. And when the next row of it has no value of column "A", the value of row is added to temp. By this, 0,123-00000-00,Test Assembly,,ABC Inc,abc123 is created. When the next row has no value of column "A", the value is added to ar by adding 4 empty values. By this, ,,,,XYZ Inc,abc234 is created. By this flow, the destination values are created.
Put the created value to the destination sheet.
Sample script:
When you use this script, please copy and paste the following script to the container-bound script of the Spreadsheet. And please set the source sheet name and destination sheet name.
function myFunction() {
const srcSheetName = "Sheet1"; // Please set the source sheet name.
const dstSheetName = "Sheet2"; // Please set the destination sheet name.
// 1. Retrieve values from the source sheet.
const ss = SpreadsheetApp.getActiveSpreadsheet();
const [header1, [,subHead1, subHead2], ...srcValues] = ss.getSheetByName(srcSheetName).getDataRange().getValues();
// 2. Create values for putting to the destination sheet an 2 dimensional array.
let temp = [];
const dstValues = srcValues.reduce((ar, [a,b,c,d]) => {
if (a.toString() != "") {
if (temp.length == 4) ar.push(temp.concat("",""));
temp = [].concat(a,b,c,d);
} else {
ar.push(temp.length == 4 ? temp = temp.concat(b,c) : Array(4).fill("").concat(b,c));
}
return ar;
}, [header1.concat(subHead1, subHead2)]);
// 3. Put the created value to the destination sheet.
ss.getSheetByName(dstSheetName).getRange(1, 1, dstValues.length, dstValues[0].length).setValues(dstValues);
}
When you run the function of myFunction(), the values are put to the destination sheet.
Note:
Please use this script with enabling V8.
If you want to use the script as the custom function, you can also use the following script. When you use this script, for example, when your sample input sheet is used, please put a custom function of =CUSTOMSAMPLE(A1:D12).
function CUSTOMSAMPLE(values) {
// 1. Retrieve values from the source sheet.
const [header1, [,subHead1, subHead2], ...srcValues] = values;
// 2. Create values for putting to the destination sheet an 2 dimensional array.
let temp = [];
const dstValues = srcValues.reduce((ar, [a,b,c,d]) => {
if (a.toString() != "") {
if (temp.length == 4) ar.push(temp.concat("",""));
temp = [].concat(a,b,c,d);
} else {
ar.push(temp.length == 4 ? temp = temp.concat(b,c) : Array(4).fill("").concat(b,c));
}
return ar;
}, [header1.concat(subHead1, subHead2)]);
// 3. Put the created value to the destination sheet.
return dstValues;
}
This sample script is for your sample input and output values in your question. When the cell structure in your actual sheet is different from them, the script might not be able to be used. Please be careful this.
References:
getValues()
reduce()
setValues(values)
I am kinda new in GAS programming and I need help in making a script to rename multiple name ranges according to cell values
I am currently using a simple script in which I am creating a name range for a selected column, and naming the range according to a cell value.
function Group_A() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var first = ss.getSheetByName("DATA VALIDATION");
var range = first.getRange("A1");
var cell = range.getCell(1,1);
var sheet = cell.getValue();
ss.setNamedRange(sheet, ss.getRange('A2:A'));
}
The name of the range will be from the first cell of the given column. The range of the name range will be from the 2nd row to the end row for the given columns.
I need help in running this code on a loop for nNum of columns as there are more than 20 name ranges to make.
Thanks in advance
I believe your goal as follows.
The 1st row is the name for using the named range.
You want to rename the named range with the new name. The range is after the row 2 in the column.
You want to select the columns on the sheet DATA VALIDATION.
You want to rename the named ranges to each column of the selected columns by giving the name retrieved from the 1st row.
For this, how about this answer?
Flow:
The flow of this sample script is as follows.
Retrieve sheet.
Retrieve the 1st row values.
Retrieve the named ranges in the sheet and create an object.
Retrieve the selection.
Retrieve each range and rename the existing named range using the name.
Sample script 1:
In this sample script, the existing named range is renamed for the selected columns. Before you run the script, please select columns in the sheet DATA VALIDATION. And then, please run the script. By this, the named range is set for each column using the name retrieved by the 1st row.
function Group_A() {
// 1. Retrueve sheet.
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName("DATA VALIDATION");
// 2. Retrieve the 1st row values.
const headerRow = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
// 3. Retrieve the named ranges in the sheet and create an object.
const namedRangesObj = sheet.getNamedRanges().reduce((o, e) => Object.assign(o, {[e.getRange().getColumn()]: e}), {});
// 4. Retrieve the selection.
const selection = sheet.getSelection();
// 5. Retrieve each range and rename the existing named range using the name.
selection
.getActiveRangeList()
.getRanges()
.forEach(r => {
const col = r.getColumn();
const name = headerRow[col - 1];
if (!name) throw new Error("No headef value.");
if (col in namedRangesObj) {
namedRangesObj[col].setName(name);
}
});
}
Sample script 2:
In this sample script, the existing named range is renamed for the selected columns. And also, when the selected column is not the named range, it is set as new named range using the name retrieved from the 1st row. Before you run the script, please select columns in the sheet DATA VALIDATION. And then, please run the script. By this, the named range is set for each column using the name retrieved by the 1st row.
function Group_A() {
// Ref: https://stackoverflow.com/a/21231012/7108653
const columnToLetter = column => {
let temp,
letter = "";
while (column > 0) {
temp = (column - 1) % 26;
letter = String.fromCharCode(temp + 65) + letter;
column = (column - temp - 1) / 26;
}
return letter;
};
// 1. Retrueve sheet.
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName("DATA VALIDATION");
// 2. Retrieve the 1st row values.
const headerRow = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
// 3. Retrieve the named ranges in the sheet and create an object.
const namedRangesObj = sheet.getNamedRanges().reduce((o, e) => Object.assign(o, {[e.getRange().getColumn()]: e}), {});
// 4. Retrieve the selection.
const selection = sheet.getSelection();
// 5. Retrieve each range and rename and set the named range using the name.
selection
.getActiveRangeList()
.getRanges()
.forEach(r => {
const col = r.getColumn();
const name = headerRow[col - 1];
if (!name) throw new Error("No headef value.");
if (col in namedRangesObj) {
namedRangesObj[col].setName(name);
} else {
const colLetter = columnToLetter(col);
ss.setNamedRange(name, sheet.getRange(`${colLetter}2:${colLetter}`));
}
});
}
Note:
In these sample scripts, it supposes that each named range is one column. Please be careful this.
Please use this script with enabling V8.
References:
getSelection()
Class Selection
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.