I\'m experimenting with Blockspring which provides a Google Sheets add-on which can, for example, run a function which returns data from a webservice e.g.
=B
Posting a useful snippet, in case you need to force all formulas to recalculate. This is considerably more performant than other examples posted here.
function forceRefreshSheetFormulas(sheetName) {
var activeSpreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var sheet = activeSpreadsheet.getSheetByName(sheetName);
var range = sheet.getDataRange();
var numCols = range.getNumColumns();
var numRows = range.getNumRows();
var rowOffset = range.getRow();
var colOffset = range.getColumn();
// Change formulas then change them back to refresh it
var originalFormulas = range.getFormulas();
//Loop through each column and each row in the sheet
//`row` and `col` are relative to the range, not the sheet
for (row = 0; row < numRows ; row++){
for(col = 0; col < numCols; col++){
if (originalFormulas[row][col] != "") {
range.getCell(row+rowOffset, col+colOffset).setFormula("");
}
};
};
SpreadsheetApp.flush();
for (row = 0; row < numRows ; row++){
for(col = 0; col < numCols; col++){
if (originalFormulas[row][col] != "") {
range.getCell(row+rowOffset, col+colOffset).setFormula(originalFormulas[row][col]);
}
};
};
SpreadsheetApp.flush();
};
There are a few tricks at work:
- Setting a cell's formula to ""
clears it, so we avoid that.
- We need to change the formula, flush, and change it back and flush again.
Refresh all table Its slow, but working!
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var rangeData = sheet.getDataRange();
var lastColumn = 38;
var lastRow = 17;
var searchRange = sheet.getRange(2,2, lastRow-1, lastColumn-1);
function forceRefresh() {
//Loop through each column and each row in the sheet.
for(i = 1; i < lastColumn; i++){
for (j = 1; j < lastRow ; j++){
var cell = searchRange.getCell(j,i);
var formula = cell.getFormula();
var tempFormula = formula.replace("=", "?");
cell.setFormula(tempFormula);
SpreadsheetApp.flush();
cell.setFormula(formula);
};
};
};
forceRefresh(); //call
Instead of using flush
, you can tell the spreadsheet which cells the function is dependent on by passing them to the formula at the end of its expected parameters. Its ok to pass more parameters than the function will use. This way the spreadsheet will assume that the value returned by the function might be different if the inputs are different and the normal update process will take care of it.
If you want it to re-evaluate when any cell changes, just pass in a range that includes all the cells.
For Example:
=MyFunction("this", "that", A1:Z10000)
If you take the time to pass in exactly the cells that the function is logically dependent on, it will be more efficient by not re-evaluating when it does not need to.
=MyFunction("this", "that", A10, C5, G6:H9 )
If it's not really dependent on any cell but you want to evaluate the function manually, on demand, make a cell that increments its hidden value when the user clicks on it (see buttons) and then pass that cell address into the function.
=MyFunction("this", "that", A10 )
// make A10 change its value on a button click
Example based on accepted answer above. Adding since I had to struggle a little to interpret how to use this to auto eval a cell formula on any sheet change. This code runs slow, but I'm sharing it as a general example in hopes it helps, YMMV.
/* Force evaluation of custom formula on sheet change.
* Temporarily changes formula text.
* @param sheetName: String Needs to be present in the formula
* @param row: Int
* @param col: Int
*/
function forceEval(sheetName, row, col){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName(sheetName);
var orig = sheet.getRange(row,col).getFormula();
var temp = orig.replace("=", "?");
sheet.getRange(row,col).setFormula(temp);
SpreadsheetApp.flush();
sheet.getRange(row,col).setFormula(orig);
}
Now call it using the onEdit trigger
function onEdit(e){
forceEval("MySheet", 1, 1)
}
This will temporarily re-write the formula in cell 1,1 replacing "=" with "?", then flush(), then change it back, forcing the evaluation after any edit in the sheet.
Custom formula in cell might look something like this: =SUMSTATUS("MySheet","InProgress")
Should work for: =BLOCKSPRING("get-stock-current-stats", "ticker", "MSFT")
The flush()
method can be used. Also see answers provided by others.
Documentation
SpreadsheetApp.flush();
Quote from the documentation:
Applies all pending Spreadsheet changes
If there were no changes to the spreadsheet, but you want to force formulas to recalculate, you will need to make a change, and then use SpreadsheetApp.flush();
For example, you could get the value from cell A1, then set the same value to cell A1. So, there is no chance of losing data because you are getting and setting the same value. The change allows. SpreadsheetApp.flush();
to recalculate all the formulas.
The documentation has some information on optimization: Google Sheets Custom Function Optimization