How to get values from unspecified number of sheets - google-apps-script

I have a spreadsheet that may have any number of sheets on it at any given time. These "Side Sheets" have a total value added and placed in a specified cell. We'll say this total is in cell "A1" on every side sheet. I want to total all of these side sheet totals, and place the total in-cell on another sheet.
I've coded a solution I think should work, but it displays "loading" forever. I'm certain there's an easier way to do this, I just can't see it.
function GETSIDESHEETTOTALS(){
var totalCell = "A1"
var total = 0;
var cur_ss = SpreadsheetApp.getActive();
cur_ss.getSheets().forEach(function(sheet){
total += sheet.getRange(totalCell).getValue();
});
return total;
}
I'm expecting the totals from each sheet to add together and display in the cell I've specified on the main sheet. I've placed the function "=GETSIDESHEETTOTALS()" into a cell on the main page of my spreadsheet. I would prefer it to be a cell-called function if possible.
Does anyone have an alternate solution, or can tell me what I'm doing wrong?

For those familiar with Excel, this could be rephrased as, "How do I use Google App Script to sum using 3D cell references?".
Briefly looking at yours, you do not exclude the sheet on which you aggregate the total. Perhaps you're recursively adding the values together?
My very quick example from scratch:
function sum3D(cellRef) {
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
var cumTotal = 0;
for (var i=0;i<sheets.length;i++){
if (sheets[i].getIndex()!==1){ // specifically omit the first sheet
cumTotal += sheets[i].getRange(cellRef).getValue();
}
}
return cumTotal;
}
This is implemented in the first sheet in my Google Sheet as "=sum3d('A1')".
However, I would recommend designing this more generally to simply return an array upon which you can perform any function (average, multiplications, etc.).
E.g.
function get3D(cellRef) {
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
var arr = [];
for (var i=0;i<sheets.length;i++){
if (sheets[i].getIndex()!==1){
arr.push( sheets[i].getRange(cellRef).getValue());
}
}
return arr;
}
and implemented as, e.g., "=sum(get3d('A1'))".
EDIT
Some parts unnecessarily separated in the code have been consolidated (but the overall function remains the same)
EDIT 2
There are obvious improvements regarding how you designate the aggregator sheet. For example, you could simply pass in the sheet name in the formula and omit that based on the return value of "sheets[i].getName()".

Related

Is it possible to add formatting (shading) to rows being appended in Google Sheets (by Google Apps Script)

I've got a Google App Script which is copying rows from one sheet to another, performing various transformations. This logic ultimately gets rows onto the new sheet using sheet.appendRow(row detail). I would like these newly created rows to have a background colour (my intention is to hold a 'latestColour' so I can alternate the shading).
So, is there anyway to add shading within the appendRow method itself, or easily determine the range that the appendRow method processed, such that I can apply additional logic to add the shading.
You can use conditional formatting
=and(A1<>"",A2="")
Although I'm not sure whether I could correctly understand your situation, from your question, I thought that you might be using [Format] --> [Alternating colors] in Google Spreadsheet. And, when a new row is appended by putting the values, you might want to reflect "Alternating colors" in the appended row. If my guess is correct, how about the following sample script?
Sample script:
function myFunction() {
const addValues = ["sample1", "sample2", "sample3"]; // This is a sample appending value. Please replace this for your value.
const sheetName = "Sheet1"; // Please set the sheet name.
// Retrieve banding object from the data range.
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
const b = sheet.getDataRange().getBandings();
if (b.length == 0) {
console.log("Bandings are not used.");
return;
}
// Append the value.
sheet.appendRow(addValues);
// Expand the range of banding.
b[0].setRange(sheet.getDataRange());
}
When this script is run, the current banding is retrieved. And, after the value was appended, the banding is updated by including the appended row. In this sample, even when the multiple rows are appended, this script can be used.
Note:
From your question, I guessed that there is one banding in the data range in your sheet. Please be careful this.
References:
getBandings()
setRange(range)
Unfortunately the method appendRow() does not receive formatting settings as input, only an array of values.
However, here is a suggestion if you want to implement your own logic:
Sample code:
function applyColorLastRow() {
var ss = SpreadsheetApp.getActive(); //get active sheets file
var range = ss.getDataRange(); //get populated range, you may want to set a range manually if needed.
var lastRowNum = range.getLastRow(); //getting the last row index of the range.
var lastRowRange = ss.getRange(`${lastRowNum}:${lastRowNum}`); //narrowing the range (using A1 notation) to the last row only to apply color
var lastRowColor = lastRowRange.getCell(1,1).getBackgroundObject().asRgbColor().asHexString();
//Your row coloring logic here...
if (lastRowColor === '#ffffff'){ //toggling white/grey color as an example...
lastRowRange.setBackground('#cccccc'); //apply grey color to all cells in the last row range
} else {
lastRowRange.setBackground('#ffffff'); //apply white color to all cells in the last row range
};
}

Changing info on a different sheet in the same spreadsheet

I have two ranges of equal size on different sheets in the same spreadsheet. I am trying to find a row (based off of user input) in the first sheet and then use that index to modify a table in the second sheet that counts how many times that certain index has been used before (to make a nice looking pie chart).
This code runs but will not produce results on the second sheet. I've gone through the debugging process and my best guess is that for some reason, my for in loop is not running through. Attached is my code that takes in the beforementioned index and attempts to perform the second half of my goal.
function acceptToEncounterChart(ghostrow) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
SpreadsheetApp.setActiveSheet(ss.getSheets()[1]);
ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Average Encounter Chart");
var range = sheet.getRange("B3:B14")
for(var i in range) {
if(ghostrow == i) {
var before = range[i][0].getValue()
range[i][0].setValue(before + 1);
}
}
SpreadsheetApp.setActiveSheet(ss.getSheets()[0]);
};
Explanation:
I am not entirely sure what is your goal.
However, here is some fixes / improvements starting from the beginning:
You define 2 times the same variable ss with exactly the same value.
You don't need to set the active sheet, if your goal is to just get the sheet, therefore this line is redundant:
SpreadsheetApp.setActiveSheet(ss.getSheets()[1]);
Variable range is not an array but a range object. You can't index it and therefore you can't also use a for loop to iterate over a single object. For the same exact reason, the code inside the if statement is wrong, you can't index range. But you don't see any errors because the if statement evaluates to false.
In JavaScript and in many other programming languages, array indexes start from 0. Since your range starts from cell B3 or row 3, you need to use i+3 to match the data with the range.
For the same reason as the previous point, ghostrow is an index, not a row. The if statement compares an array index i with ghostrow, so ghostrow should not be confused with the actual sheet row. For example, if you choose ghostrow=5 then the current script will increment the value of the cell B8 (remember i+3) by 1.
Solution:
Here is a workable code snippet:
function acceptToEncounterChart(ghostrow) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Average Encounter Chart");
var data = sheet.getRange("B3:B14").getValues().flat();
data.forEach((v,i)=>{
if(ghostrow == i){
sheet.getRange(i+3,2).setValue(v+1)
}
});
ss.setActiveSheet(ss.getSheets()[0]);
}
Related:
Please explore the official google apps script documentation.

Count any colored cells in range

I am trying to create a Gantt Chart in Google Sheets, therefore I'll need to count all the colored cells, of any color, within a specific range, in order to get the duration of such task.
However, from what I've gathered, there's no built-in function.
https://www.youtube.com/watch?v=IuMSytD9t38 is the closest thing that I've found online, especially since it solves the "auto-refresh" problem. But it's not quite it.
Can anyone point me in the right direction?
If you want to use Google Scripts, you'll use the getBackgrounds() function for the cell range.
The following code added to a sheet via Google Scripts allows you to put =colors("a1:a5") and get all the non-white cells counted. You do have to put the cell array in quotes for it to work.
You can see it working on this sheet.
function COLORS(input) {
var ss = SpreadsheetApp.getActiveSpreadsheet();//get this doc
var sheet = ss.getActiveSheet();//get the active sheet
var counter = 0;//no colors yet
var range = sheet.getRange(input);//get range of cells from the function
var bgColors = range.getBackgrounds();//get the array of background colors
bgColors.forEach(function(element){
var cleanColors = arrayRemove(element,'#ffffff');//kick out the white backgrounds
counter = counter + cleanColors.length;//count them up
})
return counter;//return the total count
}
//kick things out of arrays from https://love2dev.com/blog/javascript-remove-from-array/#create-remove-method
function arrayRemove(arr, value) {
return arr.filter(function(ele){
return ele != value;
});
}
to count background color you can use this addon:
https://chrome.google.com/webstore/detail/custom-count-and-sum/njiklelndjpdbdngfkdgeijcpfabfgkb
and then you can simply use this formula:
=COUNTBACKGROUNDCOLOR("A2:A20", "A3")
A2:A20 being the range and A3 being the example of color to count

I have a pseudocode, but can't code yet

thank you and sorry for my incredibly unexperienced question in advance. So, I want to make a code and I know what I want it to do, I just don't know how to program. What I need is:
function GenPre()
1.- delete range Presupuesto!A12:C42
2.- copy range Imp!A2:Imp!C33 VALUES in Presupuesto!A12:Presupuesto!C42 (Imp cells are formulas, and I want to copy just the values)
3.- show only used rows in column A in Presupuesto!A12:A42 (consider some rows will be already hidden, so unhiding them first would be an idea)
4.- go to sheet Presupuesto (once I do this function, I want to end up on the sheet Presupuesto
end Generar
This function will be runned by a button in another sheet in the same spreadsheet.
and so far, I have this:
function GenPre() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetbyname(Presupuesto);
//next step is to select and delete the content of the range on the sheet
}
I know I'm asking for much, I just can't find much about selecting defined cells... and I really don't know how to program yet.
Thanks a bunch!!
Edit
So, I started tweaking with what k4k4sh1 answered and got this (AND reading other posts on hiding rows containing "x" on a given cell):
function GenPre() {
var sheetp = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Presupuesto') //name a variable to the sheet where we're pasting information
var sheetc = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Imp') //name a variable to the sheet frome where we're copying information
sheetp.getRange('a12:c41').clearContent() //delete all values in the range where we're copying
sheetc.getRange('A2:C31').copyValuesToRange(sheetp,1,3,12,41); //copy from source range to destination range
sheetp.showRows(12,41); //make sure all rows in the destination range are shown
for( i=12 ; i<=41 ; i++) {
if (sheetp.getRange('A'+i).getValue() == '') { // status == ''
sheetp.hideRows(i);
}
}
}
Te script is running how it should, but now, I want it to run faster (takes 12 seconds to run, when it doesn't really look that heavy), and is there a function to switch my view to sheetp? thank you all!
You're asking us to do all the work :)
Let's start from your piece of code:
the method .getSheetByName(shName) accepts a string as argument, so you should change it to
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Presupuesto');.
Mind that JavaScript is case-sensitive, so .getSheetbyname is not the same as .getSheetByName().
According to Sheet Class Reference use sheet.getRange() to get your Range Object. Take a look to Range Class Reference: to clear the range content including formats use .clear(), to clear just the content leaving the formatting intact use .clearContent().
To hide unused rows try:
function hideRows(sheetName, column) {
var s = SpreadsheetApp.getActive().getSheetByName(sheetName);
s.showRows(1, s.getMaxRows());
s.getRange(column)
.getValues()
.forEach(function (r, i) {
if (r[0] == '') {s.hideRows(i + 1);}
});
}
// hideRows('Presupuesto', 'A12:A42');

Google Drive - How to Autopopulate Google Spreadsheet with Concatenate functions when users submit data via webform?

I have a Google Drive Spreadsheet in which I'm trying to have a formula autopopulate all the way down the rows, as users submit data via a webform attached to the spreadsheet. The tricky part is that I use a CONCATENATE function already, to perform a concatenation of the data submitted on several columns into one single cell.
However, for the CONCATENATE function to work, I have to "apply" it to newly submitted rows.
Is there a way to automate the filling of this formula down the rows in the spreadsheet?
I've tried to place an ArrayFormula function to it, even setting the range (A1:A), but I couldn't find the proper syntax for it work, if it may like this.
The function goes:
=CONCATENATE(CHAR(10)&X14&V14&Y14&J14&" "&K14&" "&L14&M14&N14&O14&" "&P14&" "&Q14&R14&S14&CHAR(10)&T14&"."&CHAR(10)&U14&"."&CHAR(10)&W14&CHAR(10)&CHAR(10)&CHAR(10)&I14&CHAR(10)&Z14&" "&AA14&CHAR(10)&AB14&AC14&AD14&AE14&AF14&AG14&AH14&"."&CHAR(10)&AI14&AJ14)
Any suggestion will be greatly appreciated.
(Answered by the OP in a question edit. Transformed into a community wiki answer. See Question with no answers, but issue solved in the comments (or extended in chat) )
The OP wrote:
I've found a little script from the Script Gallery of Google Spreadsheet (AutoFormulas, by tuxincarnate[#]gmail[dot]com), which solves the trick! Just tested it with a dozen of submissions from the webform and it does what it promises, autopopulate with formulas down the columns where applied.
// Updates all rows except the last to have the same formulas as row 3 (allowing for a header and different row 2 formula)
// To activate this functionality, the last row in the column should have the value '(auto)'
function UpdateFormulas() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheetCount = ss.getNumSheets();
for (var sheetIndex = 0; sheetIndex < sheetCount; ++sheetIndex) {
var sheet = ss.getSheets()[sheetIndex];
var rowCount = sheet.getMaxRows();
var columnCount = sheet.getMaxColumns();
if (rowCount < 5) continue;
for (var columnIndex = 1; columnIndex <= columnCount; ++columnIndex) {
if (sheet.getRange(rowCount, columnIndex).getValue() == '(auto)') {
var row3Range = sheet.getRange(3, columnIndex);
for (var rowIndex = 4; rowIndex < rowCount; ++rowIndex) {
if (sheet.getRange(rowIndex, columnIndex).isBlank()) {
row3Range.copyTo(sheet.getRange(rowIndex, columnIndex));
}
}
}
}
}
}
#David Tew wrote:
Whilst you have a solution already, you might like to consider for the future the following formula, which is what you were originally looking for (You don't need to use CONCATENATE since you are choosing the columns to join together with & symbol)
=arrayformula(CHAR(10)&X2:X&V2:V&Y2:Y&J2:J&" "&K2:K&" "&L2:L&M2:M&N2:N&O2:O&" "&P2:P&" "&Q2:Q&R2:R&S2:S&CHAR(10)&T2:T&"."&CHAR(10)&U2:U&"."&CHAR(10)&W2:W&CHAR(10)&CHAR‌​(10)&CHAR(10)&I2:I&CHAR(10)&Z2:Z&" "&AA2:AA&CHAR(10)&AB2:AB&AC2:AC&AD2:AD&AE2:AE&AF2:AF&AG2:AG&AH2:AH&"."&CHAR(10)&‌​AI2:AI&AJ2:AJ)
this should go in the second row
The OP wrote:
#DavidTew showed me a clear and easy way to have it solved. The ArrayFormula should go alone, telling it to put together the ranges for every column into every single row. Works like charm. Proper syntax is: =arrayformula(A2:A&B2:B&C2:C), in order to have contents from cells A2, B2 and C2 "concatenated" into the cell where the ArrayFormula is applied. The most important issue is that by using this function, it autpopulates all the way down the rows as users submit data via a webform attached to the spreadsheet.