I'm trying to increment a number in a Google sheet cell by one, when I custom menu item is clicked. But the result in the spreadsheet is either #NUM! or 1range. I've tried a number of different methods, as shown in the comments in my code.
Note that the number is stored as a custom number format so that it has 4 leading zeros, eg: 00001
function onOpen() {
var ui = SpreadsheetApp.getUi();
// Or DocumentApp or FormApp.
ui.createMenu('OrderNumber')
.addItem('Generate next order number', 'menuItem1')
.addToUi();
}
function menuItem1() {
SpreadsheetApp.getUi() // Or DocumentApp or FormApp.
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var range = sheet.getRange("N11");
range = 1 + +range;
// For the previous line, I've also tried: range = 1 + parseInt(range)
// For the previous line, I've also tried: range = 1 + Number(range)
SpreadsheetApp.getActiveSheet().getRange('N11').setValue(range);
}
As a test if it just set it to a number it shows in the spreadsheet correctly, e.g:
range = 5
SpreadsheetApp.getActiveSheet().getRange('N11').setValue(range);
Any help on this would be greatly appreciated. Thanks.
A Range is an object containing a lot of information: values but also formatting, data validation, font size etc.
You need to call getValue() on range first to extract the number:
var range = sheet.getRange("N11").getValue();
Related
I am attempting to create a virus-simulation in Google Sheets for a high school unit on exponential functions. I would like to set R0 and then simulate spread each day by running a script that:
-- reads a range of rows and columns (each cell simulates a person)
-- leaves the cell alone if it has already been infected (i.e., changed color)
-- randomly changes the color of the appropriate number of cells using a random number generator and the equation R0^N, where N is the number of days
This is my first script in Google Sheets, and it is frustrating. I am feeling the novice frustration. Below is my current iteration. Does anyone have suggestions
function ViralSpread() {
var sheet = SpreadsheetApp.getActive().getSheetByName("ModelingTransmission");
var range = SpreadsheetApp.getActive().getRangeByName("People");
var values = range.getValues();
i = 1;
values.forEach(function(row) {
j=1;
row.forEach(function(col) {
j++;
var testrange = SpreadsheetApp.getActiveSheet().getRange(i,j);
var testvalue = testrange.getValues();
var newdata = [j];
testvalue.setvalues(newdata);
});
i++;
});
}
The conditional formatting does not help with that. I tried many scripts that are published online but non of them works.
Same issue is here: https://support.google.com/docs/thread/4469470?hl=en
Answer:
Unfortunately, this is not possible to do.
More Information:
The main reaason that thids can't be done is that the Sheets API nor Google Apps Script have listeners for when the active cell on a sheet changes. There are also no clicker or button press listeners, if the clicks or button presses do not make an edit or change to the Sheet.
Testing & Reasoning:
I had a mess around with Apps Script to see if I could create some sort of workaround, though this ended up with encountering other obstacles.
There is a way of making the background of the active row change colour, which can be easily done in Apps Script with something like:
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var currRow = ss.getActiveCell().getRow();
sheet.getRange(currRow + ":" + currRow).setBackground("#F4C2C2");
Knowing this, and knowing that I couldn't call a trigger from just a click, I created a Sheets add-on instead with with a JavaScript function embedded in the of the HTML document which calls the Apps Script method at a specific time-interval:
<script>
function poll() {
setInterval(update, 500);
}
function update() {
google.script.run.rowHighlighter();
}
</script>
with the following rowHighlighter() function inside the code.gs file:
function rowHighlighter() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var currRow = ss.getActiveCell().getRow();
var l = PropertiesService.getUserProperties().getProperty("currRow");
if (l) {
sheet.getRange(l + ":" + l).setBackground("#FFFFFF");
}
sheet.getRange(currRow + ":" + currRow).setBackground("#F4C2C2");
PropertiesService.getUserProperties().setProperties({"currRow": currRow});
}
I thought it might be possible to call this function from within the sidebar of the Sheets add-on and use the PropertiesService class of Apps Script to store the currently selected row so that when clicked elsewhere the colour could be reset.
While this appeared to have positive results, this only lasted a few seconds - unfortunately after this the Apps Script quotas get hit and the errors in the console as seen from the Apps Script editor end show that some throttling is needed so not so many Apps Script requests are made. Of course, while this is something that you could do, increasing the interval at which the update() function is called in the add-on <body> reduced the real-timeness of the application and the whole functionality breaks.
In short; this can't be done. Both Apps Script and the Sheets API lack the functionality, and trying to build something that emulates it yourself will end in you using all your quota as the account limitations are hit.
References:
Google Apps Script - Extending Google Sheets
G Suite Developer - Extending Google Sheets with Add-ons
Google Apps Script - Properties Service
Docs editors herlp - Highlight (or mark) the row the current cell is on
function onSelectionChange(e){
const range = e.range;
const sheet = range.getSheet();
const currRow = range.getRow()
const maxRows = sheet.getMaxRows();
const maxColumns = sheet.getMaxColumns();
sheet.getRange(1, 1, maxRows, maxColumns).setFontWeight("normal");
sheet.getRange(currRow, 1, 1, maxColumns).setFontWeight("bold");
}
Regarding Devendra Pathak's solution, how do we tell the script to not act on selections where it is the selected range is lower than row 5? For example, click on A6, the script executes. Click on A1, no changes, A6 remains highlighted. Click on D7, the script executes. Row 6 formatting becomes normal, and row 7 gets the special formatting. As well, how does the script know to only format up to column 7?
Variation below.
https://docs.google.com/spreadsheets/d/1R4GPipk9FyucRcKeXcosbKmXxUTGmuaoQDaUoVDVorU/edit#gid=0
//Seeking: Act only when range is row 6 or higher
Highlight row and column of cell where the mouse is clicked:
function onSelectionChange(e) {
const range = e.range;
const sheet = range.getSheet();
const maxRows = sheet.getMaxRows();
const maxColumns = sheet.getMaxColumns();
sheet.clearConditionalFormatRules();
var formula = '=A1<>"1239"';
//---------------------------------------------------------------
//RULE1
var range1 = sheet.getRange(range.getRow(), 1, 1, maxColumns);
var rule1 = SpreadsheetApp.newConditionalFormatRule()
.whenFormulaSatisfied(formula)
.setBackground('lightgray')
.setBold(true)
.setRanges([range1])
.build();
var rules1 = sheet.getConditionalFormatRules();
rules1.push(rule1);
sheet.setConditionalFormatRules(rules1);
//---------------------------------------------------------------
//RULE2
var range2 = sheet.getRange(1, range.getColumn(), maxRows, 1);
var rule2 = SpreadsheetApp.newConditionalFormatRule()
.whenFormulaSatisfied(formula)
.setBackground('lightgray')
//.setBold(true)
.setRanges([range2])
.build();
var rules2 = sheet.getConditionalFormatRules();
rules2.push(rule2);
sheet.setConditionalFormatRules(rules2);
}
//Sample sheet here
Hi,
I am using formulas to calculate an array N:R. Once calculated, I want to determine the last row of the array with a non-empty cell (the empty cells are not blank).
What I can do so far:
Return the last non-empty cell of a column
=INDEX(FILTER(O:O,O:O<>""), ROWS(FILTER(O:O,O:O<>"")))
or the row of the filter selection (in my case 25 in the filter selection vs 38 in the sheet)
=ROWS(FILTER(O:O,O:O<>""))
What I haven't figured out is how to:
Do this search for the whole array and not just one row at a time
Return the row of the last non-empty cell in the array
Cheers
For a formulaic approach, you can try
=max(filter(row(N2:N), MMULT(N(N2:R<>""), transpose(column(N2:R2)^0))>0))
This custom function will do it. Sometimes scripts are way easier than some of the bizarre formulas that arise (IMHO). It just loops through the data row by row and notes the row number if it finds data ie cell.value() != ""
function findHighestNonEmptyRow(dummyRange){
var sheet = SpreadsheetApp.getActive();
var range = sheet.getRange("N:R");
var valuesRC = range.getValues();
var numRows = range.getNumRows();
var numCols = range.getNumColumns();
var highestNonEmptyRow = 0;
for (var row = 0; row < numRows; row++) {
for (var col = 0; col < numCols; col++) {
if (valuesRC[row][col] != ""){
highestNonEmptyRow = row+1; // +1 to offset loop variable
}
}
}
Logger.log(highestNonEmptyRow);
return highestNonEmptyRow;
}
Log show correct value of 38. You can delete the Logger.log(highestNonEmptyRow); line when you have tested.
I put the formula in W44 in your test sheet....
EDIT: Due to feedback that all was not as expected...
There was a typo in the first script: This line var range =
sheet.getRange("N:D"); should have been var range =
sheet.getRange("N:R");
I found out that Google scripts caches the result of custom
formulas, and just returns the cached value, even if things on the
sheet have changed. This is bizarre behavior, but is intended to
reduce CPU time. The workaround is to pass in a range that is likely
to change, and this causes the function to recalculate. I updated
the formula and the called the function like this:
=findHighestNonEmptyRow(N2:R42)
and hey it all works!
Stick to the formula... however, we both learned a lot from your
question I think, so thanks for that!
Spreadsheet data looks like this
function myFunction()
{
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getSheetByName('Active Listeners');
sh.insertRowBefore(15551)
}
As i have large range of rows that could be work on. If the value of the range matches with "Apr 9" then insert row before to that. Could anyone help me to get that.
A 'for loop' to cycle through your rows from the bottom would almost do the trick. The loop inserts a row after each row specified by i. Keep in mind you'll need a different solution if your Apr 9 column is formatted as a date. This works for plain text only. You can select the column and change to plain text with "Format > Number > Plain Text" on the menu.
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getSheetByName('Active Listeners');
//var shlen = sh.getDataRange().getLastRow(); //returns integer last row
var shlen = Browser.inputBox("Enter Last Row of Preferred Range", Browser.Buttons.OK_CANCEL);
var ecell = sh.getActiveCell().getA1Notation();
You may need a different dataRange (below), I've just grabbed the parameters of data in your whole sheet (above), then grabbed a range specified in A1 notation of "A1:B[number reference of bottom row]" The modification may be that you need "B1:C" + [shlen] or whichever other range.
if (shlen >= 1) {
var dataRange = sh.getRange("A1:A" + shlen).getValues();
for (var i = shlen; i > 0; i--) {
var row = dataRange[i-1];
if (row[0] == "Apr 9") {
sh.insertRowAfter(i-1)
}
}}
}
Someone more knowledgeable than me can pitch in if they have a better answer, but my only solution (which should be ok if it's a one-of) would be to just repeat the script a few times, starting at the row of your choice each time. Select cell A1 and then press "control (or command) + down arrow". It will take you to the first gap, which should be where the previous script ended. Remember the row number you're up to and plug that in the input box when you run the script again. Might take a few iterations but you'll get there.
If this process is going to be done repeatedly then best of luck in finding a solution :)
I'm using a function to count the number of colored cells, but when they change color (not value), the spreadsheet doesn't recognize a value change and my formula doesn't update with the new number of colored cells.
To manually force an update, I have to add a character to that cell, then remove it, so that the sheet will recognize a value change and recount the number of colored cells. Is there a way to automate this and force the sheet to add and remove a character in order to force an update?
Hopefully, this is clear enough to be answerable.
You can simply use onEdit function if there is any data update on the cells. But for your case, I have tested below scripts works good, but I am still confused it is the best practice of coding, because I could not find any other options to solve your question.
//Below function will count the number of cells with the specified background color
function countWhereBackgroundColorIs(color, rangeSpecification) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var range = sheet.getRange(rangeSpecification);
var bgColors = range.getBackgrounds();
var k = 0;
for (var i in bgColors) {
for (var j in bgColors[i]) {
if(bgColors[i][j] == color)
k = k +1;
}
}
return k;
}
//Below function will append a space" " on the cell A1 data and then remove the appended char. This is for updating the range. Make sure that at least a cell mentioned in above function countWhereBackgroundColorIs() is getting updated. Here Used 'A1'
function updateSheet()
{
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var cell = sheet.getRange("A1");
var d = cell.getValue();
cell.setValue(d+" ");
SpreadsheetApp.flush();
cell.setValue(d);
SpreadsheetApp.flush();
}
//creating a time based trigger to execute the updateSheet() function for every one min. Minimum time is 1min. This trigger creation is a one time activity and this can be done in GUI also Resources>All your triggers.
function createTimeDrivenTriggers() {
ScriptApp.newTrigger('updateSheet')
.timeBased()
.everyMinutes(1)
.create();
}