I am using the following script to refresh my Google Finance watchlist. I am not a programmer so that's why I copied this from somewhere and use it as long as it does the trick. It certainly works for me but it's kinda slow, and I hope someone can help me to optimize this script to make it better.
My understanding is, this script basically scan the whole table (7 columns and over 60 rows) to find out the no. of rows and column. It first empty the cells then fill the cell with original formula (which is GoogleFinance function for stock quote, something like that) to force the update for the whole table. I actually just need to trigger update for a specific column but I don't know how to do it (yep, not a programmer..). I tried to change the row = x and col = x to another values and it would help to reduce the scope of the update size of the table, and it help me abit only.
In order to make it update more frequently, I actually copy and paste those two for loops set 5 more times and use Utilities.sleep(10000); in between to make the script run 6 rounds a minute. So I can bypass the minimum 1 minute interval in Google Sheet setting. But yes, I know it's cause the script run even slower...
I hope someone can help me to speed this script up, really appreciate anyone can help! Thanks you!!
function forceRefreshSheetFormulas(sheetName) {
var activeSpreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var sheet = activeSpreadsheet.getSheetByName('*Table Name*');
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();
Try
pls enable Sheets API at Advanced Google services.
function forceRefreshSheetFormulas(sheetName) {
var activeSpreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var sheet = activeSpreadsheet.getSheetByName('*Table Name*');
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
var ranges = []
var blank = []
var original = []
for (row = 0; row < numRows; row++) {
for (col = 0; col < numCols; col++) {
if (originalFormulas[row][col] != ""){
ranges.push( `'*Table Name*'!${columnToLetter(col + colOffset)}${row + rowOffset}` )
blank.push('')
original.push(`${originalFormulas[row][col]}`)
};
}
};
updateGoogleSheet(ranges,blank)
SpreadsheetApp.flush();
updateGoogleSheet(ranges,original)
};
function updateGoogleSheet(ranges,values) {
var spreadsheetId = SpreadsheetApp.getActiveSpreadsheet().getId()
var data = ranges.map((e, i) => ({ range: e, values: [[values[i]]] }));
Sheets.Spreadsheets.Values.batchUpdate({ data, valueInputOption: "USER_ENTERED" }, spreadsheetId);
}
function columnToLetter(column) {
var temp, letter = '';
while (column > 0) {
temp = (column - 1) % 26;
letter = String.fromCharCode(temp + 65) + letter;
column = (column - temp - 1) / 26;
}
return letter;
}
reference
batchupdate
Related
function getNamedRange( e ) {
const sheet = SpreadsheetApp.getActiveSheet();
const activeRange = e.range;
const namedRange = sheet.getNamedRange( activeRange.getA1Notation() );
//alert named range
SpreadsheetApp.getUi().alert( namedRange );
return namedRange;
}
I have tried multiple ways of doing this to no avail.
Main goal: get the name of the named range where the edited cell is found
So if A1:C5 was a named range of "firstRange"
and I edited cell A2, the onEdit(e) would run getNamedRange(e) and alert "firstRange"
I have tried getName() and all sorts of combos using the reference section
Google Reference Link
First, when I saw this question and Programmatically test if a Google Sheet cell is in a named range, also I thought that this might be the same situation. But, I noticed that in my answer, the intersection ranges between the specific range and the specific named range are retrieved. I thought that the basic method is the same. So in order to use my answer for this question, it is required to modify a little. So in this answer, I would like to propose the sample script for achieving the goal by modifying it.
Sample script 1:
When this sample script is modified for your script, it becomes as follows.
function getNamedRange(e) {
var inputRange = e.range;
var columnToLetter = function (column) { // <--- https://stackoverflow.com/a/21231012/7108653
var temp, letter = '';
while (column > 0) {
temp = (column - 1) % 26;
letter = String.fromCharCode(temp + 65) + letter;
column = (column - temp - 1) / 26;
}
return letter;
};
var res = [];
var result = [];
var sheet = SpreadsheetApp.getActiveSheet();
var namedRanges = sheet.getNamedRanges();
for (var i = 0; i < namedRanges.length; i++) {
var nr = namedRanges[i];
// Retrieve a1Notations from "inputRange".
var iStartRow = inputRange.getRow();
var iEndRow = iStartRow + inputRange.getNumRows() - 1;
var iStartColumn = inputRange.getColumn();
var iEndColumn = iStartColumn + inputRange.getNumColumns() - 1;
var irA1Notations = [];
for (var j = iStartRow; j <= iEndRow; j++) {
var temp = [];
for (var k = iStartColumn; k <= iEndColumn; k++) {
temp.push(columnToLetter(k) + j);
}
Array.prototype.push.apply(irA1Notations, temp);
}
// Retrieve a1Notations from "myNamedRange".
var namedRange = nr.getRange();
var nStartRow = namedRange.getRow();
var nEndRow = nStartRow + namedRange.getNumRows() - 1;
var nStartColumn = namedRange.getColumn();
var nEndColumn = nStartColumn + namedRange.getNumColumns() - 1;
var nrA1Notations = {};
for (var j = nStartRow; j <= nEndRow; j++) {
for (var k = nStartColumn; k <= nEndColumn; k++) {
nrA1Notations[columnToLetter(k) + j] = null;
}
}
// Retrieve intersection ranges.
result = irA1Notations.filter(function (e) { return nrA1Notations.hasOwnProperty(e) });
if (result.length > 0) {
res.push(nr.getName())
}
}
if (res.length == 0) return;
SpreadsheetApp.getUi().alert(res.join(","));
}
Sample script 2:
In this case, I thought that the following simple script might be able to be used.
function getNamedRange(e) {
const range = e.range;
const sheet = SpreadsheetApp.getActiveSheet();
const r = sheet.getNamedRanges().filter(r => {
const temp = r.getRange();
const startRow = temp.getRow();
const endRow = startRow + temp.getNumRows();
const startCol = temp.getColumn();
const endCol = startCol + temp.getNumColumns();
return (range.rowStart >= startRow && range.rowStart <= endRow && range.columnStart >= startCol && range.columnStart <= endCol) ? true : false;
});
if (r.length == 0) return;
SpreadsheetApp.getUi().alert(r.map(f => f.getName()).join(","));
}
Note:
When you edit a cell, when the edited cell is included in the named range, a dialog is opened. And, you can see the name of the named range.
From this question, it seems that you are using getNamedRange as the installable OnEdit trigger. In the above scripts, you can also use the simple trigger. So you can also modify the function name from getNamedRange to onEdit.
In your script, return namedRange; is used. But when getNamedRange is run using the installable OnEdit trigger, I thought that return namedRange; is not used.
Reference:
Related thread.
Programmatically test if a Google Sheet cell is in a named range
I want to check if there is any blank cells in a given range and trying to use below code for that. Problem is Range is not certain and subject to change with every iteration.
I tried something like getRange('A'+ row : 'H'+ row) but its in wrong syntax. Can someone help me with this issue ? Thanks!
var sheet1 = spreadsheet.getSheetByName('Red'); // Get worksheet
var endRow = sheet1.getLastRow();
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet(); // Get current active spreadsheet.
var sheet2 = spreadsheet.getSheetByName('Template');
var runloop = true;
var startRow = 3;
for (var row = startRow; row <= endRow; row++) {
var sheet_name = sheet1.getRange("A" + row).getValue(); // Get the JD number for the file name.
var range = sheet1.getRange('A' + row: 'H' + row);
if (range.isBlank()) {
# Dome Something here
}
}
You can concatenate the A1Notation before passing it to getRange()
Example:
function myFunction() {
for (var row = 1; row <= 5; row++) {
var rangeA = "A" + row;
var rangeB = "H" + row;
var range = rangeA + ":" + rangeB;
Logger.log(range);
// Get the JD number for the file name.
var sheet_name = SpreadsheetApp.getActiveSheet().getRange(rangeA).getValue();
var range = SpreadsheetApp.getActiveSheet().getRange(range);
if (range.isBlank()) {
Logger.log(range.getA1Notation() + " is blank!.");
}
}
}
Output log from the example:
You can try Utilities.formatString
var range = sheet1.getRange(Utilities.formatString('A%s:H%s', row, row));
For V8 runtime
const range = sheet1.getRange(`A${row}:H${row}`);
I have this cute formula:
=sort(A1:A416,arrayFormula(randbetween(sign(row(A1:A416)),C1)),true)
I thought I could change the randomization event when I changed the value in C1, but now I see this is changing continuously when I update the sheet with other values.
How can I keep this from happening?
I only want the randomization to happen when I change the seed in C1.
Try using this edit trigger in your script:
function onEdit(e) {
if(e.range.getA1Notation() == 'C1') {
var sheet = e.source.getActiveSheet();
var firstRow = 1;
var lastRow = 416;
var col = 1;
var seedValue = Number(e.range.getValue());
if(seedValue > 1) {
for(var row = firstRow; row < lastRow; row++) {
var cellValue = Math.ceil(Math.random() * seedValue);
var cell = sheet.getRange(row, col);
cell.setValue(cellValue);
}
}
var range = sheet.getRange(firstRow, col, lastRow, 1)
range.sort(1);
}
}
I hope this is what you wanted to achieve with your formula.
I have a script scheduled every evening to look at a number of different sheets and draw borders around any new data entries.
The script works yet is inconsistent. When I check it works completely for some google sheets and partially for others. There are about 10 internal sheets on each google spreadsheet and the borders are drawn in for some and not for others.
I don't understand why it is not running clean.
function addBorders() {
//Access the sheet which contains all the IDs
var idSheet = SpreadsheetApp.openById('example');
//Check for the last row
var lastRow = 0;
var currRow = 2;
//Loop through the sheet and find the last ID available
while (idSheet.getRange("A" + currRow).getValue() != "")
{
currRow = currRow + 1;
}
//Set the last row with an ID found to be the lastRow
lastRow = currRow;
idSheet.getRange("B1").setValue("Total Rows");
idSheet.getRange("B2").setValue(lastRow);
//Loop through the ID sheet to find the ID to make changes to
for( var y = 2; y < lastRow; y++)
{
var studentID = idSheet.getSheetByName("Sheet1").getRange(y, 1).getValue();
//As IDs are take from the ID sheet, open the relevant sheet and run the code below
var ss = SpreadsheetApp.openById(studentID);
var sheetsCount = ss.getNumSheets();
var sheets = ss.getSheets();
//For each sheet in the individal student spreadsheets, set the borders correctly
for (var i = 0; i < sheetsCount; i++)
{
var sheet = sheets[i];
var range = sheet.getRange(6, 3, 35);
var values = range.getValues().map(function(d){ return d[0] });
//clear previous border
var selection = sheet.getRange(6,2,35,5)
selection.setBorder(false,false,false,false,false,false);
//set border
var index = values.indexOf("");
var border = sheet.getRange(5, 2, index+1, 5);
border.setBorder(true, true, true, true, true, true);
}
}
}
This is incorrect while (idSheet.getRange("A" + currRow).getValue() != "") you must specify a sheet (i.e. tab) example: idSheet.getRange( "Sheet1!A" + currRow).getValue()!="") If this works then it's probably always using ss.getSheets()[0] the most left sheet.
This will result in not accessing the data in the last row
for( var y = 2; y < lastRow; y++)
How do you know that the index below will never be -1. If it is then index + 1 = 0 which is not a legal row value. Seems a little flaky to me.
var index = values.indexOf("");
var border = sheet.getRange(5, 2, index+1, 5);
Spreadsheet.getRange
I'm looking for some assistance on find the row number for a cell that contains a specific value.
The spreadsheet has lots of data across multiple rows & columns. I'm looping over the .getDataRange and can located the cell containing the value I'm looking for. Once found I'd like to know the row that his cell is in, so that I can further grab additional cell values since I know exactly how many rows down and/or columns over the additional information is from the found cell.
Here is a bit of the code for finding the cell containing a specific string.
function findCell() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var dataRange = sheet.getDataRange();
var values = dataRange.getValues();
for (var i = 0; i < values.length; i++) {
var row = "";
for (var j = 0; j < values[i].length; j++) {
if (values[i][j] == "User") {
row = values[i][j+1];
Logger.log(row);
}
}
}
}
When outputting Logger.log(row) it gives me the values I'm looking for. I want to determine what row each value is in, so I can then go down X number of rows and over X columns to get the contents of other cells.
I don't know much about google apps but it looks like [i] is your row number in this circumstance.
So if you did something like:
function findCell() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var dataRange = sheet.getDataRange();
var values = dataRange.getValues();
for (var i = 0; i < values.length; i++) {
var row = "";
for (var j = 0; j < values[i].length; j++) {
if (values[i][j] == "User") {
row = values[i][j+1];
Logger.log(row);
Logger.log(i); // This is your row number
}
}
}
}
This method finds the row number in a particular column for a given value:
function rowOf(containingValue, columnToLookInIndex, sheetIndex) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets();
var dataRange = sheet[sheetIndex].getDataRange();
var values = dataRange.getValues();
var outRow;
for (var i = 0; i < values.length; i++)
{
if (values[i][columnToLookInIndex] == containingValue)
{
outRow = i+1;
break;
}
}
return outRow;
}
But if you do it like that, it won't refresh unless you change any of the parameters, that's because a custom function should be deterministic, that is, for the same parameters, it should always give the same result.
So instead, it's better to do it this way:
function rowOf(containingValue, range) {
var outRow = null;
if(range.constructor == Array)
{
for (var i = 0; i < range.length; i++)
{
if(range[i].constructor == Array && range[i].length > 0)
{
if (range[i][0] == containingValue)
{
outRow = i+1;
break;
}
}
}
}
return outRow;
}
In this case, you need to pass the full column range your looking for like so:
rowOf("MyTextToLookFor", 'MySheetToLookIn'!A1:A)
Where you would replace A by the colum of your choice, and MySheetToLookIn by your sheet's name and MyTextToLookFor by the text you are looking for.
This will allow it to refresh on adding rows and removing rows.
Since 2018 this can be done without a loop using flat().
function findRow(searchVal) {
var sheet = SpreadsheetApp.getActiveSheet();
var data = sheet.getDataRange().getValues();
var columnCount = sheet.getDataRange().getLastColumn();
var i = data.flat().indexOf(searchVal);
var columnIndex = i % columnCount
var rowIndex = ((i - columnIndex) / columnCount);
Logger.log({columnIndex, rowIndex }); // zero based row and column indexes of searchVal
return i >= 0 ? rowIndex + 1 : "searchVal not found";
}
ES6 gave us a one liner for this (but expanded for full detail).
function findRow() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss. getActiveSheet(); // OR GET DESIRED SHEET WITH .getSheetByName()
const dataRange = sheet.getDataRange();
const values = dataRange.getValues();
const columnIndex = 3 // INDEX OF COLUMN FOR COMPARISON CELL
const matchText = "User"
const index = values.findIndex(row => row[columnIndex] === matchText)
const rowNumber = index + 1
return rowNumber
}
This can be done with the Text Finder:
function findRow(searchValue){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var thisSheet = ss.getActiveSheet();
var tf = thisSheet.createTextFinder(searchValue)
var thisRow = tf.findNext().getRow()
return thisRow
}
Discovered through this post: Google App Scripts find text in spreadsheet and return location index
Implementing: https://developers.google.com/apps-script/reference/spreadsheet/text-finder
To find a row in a Google Sheets sheet based on the value of a specific column (in this case, the invoice_id column) and edit another column (in this case, the send column) in that row, you can use the following script:
// Replace "Sheet1" with the name of your sheet
var sheet = SpreadsheetApp.getActive().getSheetByName("Sheet1");
// Replace "A" with the column letter for the invoice_id column
// and "B" with the column letter for the send column
var invoiceIdColumn = "A";
var sendColumn = "B";
// Replace "12345" with the invoice_id you want to search for
var invoiceIdToSearch = "12345";
// Find the row number of the row with the matching invoice_id
var data = sheet.getDataRange().getValues();
var row = data.findIndex(row => row[invoiceIdColumn - 1] == invoiceIdToSearch) + 1;
// If a row with the matching invoice_id was found
if (row) {
// Set the value of the send column in that row to "true"
sheet.getRange(row, sendColumn).setValue(true);
}
This script will search through the entire sheet for a row with an invoice_id value that matches the invoiceIdToSearch variable, and then set the value of the send column in that row to true.
Note that this script assumes that the invoice_id column is the first column in the sheet (column A) and the send column is the second column (column B). If your columns are in different positions, you will need to adjust the invoiceIdColumn and sendColumn variables accordingly.