Collapse all groups of certain depth in Google Sheet - json

I am trying to create a google sheet that allows the view of several levels of details.
The idea is to use the macro feature to use shortcuts that run one macro for each level of detail. Each level of detail shows the active sheet with at a certain depth of groups.
i.e. when I run the macro for the most top level view all row groups are collapsed:
function _1stLevelofDetail() {
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.getActiveSheet().collapseAllRowGroups();
};
when I run the macro for the most detailed view (in my case the 4th) all row groups are expanded:
function _4thLevelofDetail() {
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.getActiveSheet().expandAllRowGroups();
};
How can I change the following code so it is robust enough that I can update/add/remove row columns without braking the code
function _3rdLevelofDetail() {
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.getActiveSheet().expandAllRowGroups();
spreadsheet.getActiveSheet().getRowGroup(8, 3).collapse();
spreadsheet.getActiveSheet().getRowGroup(11, 3).collapse();
spreadsheet.getActiveSheet().getRowGroup(14, 3).collapse();
spreadsheet.getActiveSheet().getRowGroup(18, 3).collapse();
spreadsheet.getActiveSheet().getRowGroup(23, 3).collapse();
};
My idea is to get all row groups of depth 3 but I don't know how to put that into code.

The problem with your code is that Spreadsheet operations are sometimes bundled together to improve performance
This means that e.g. spreadsheet.getActiveSheet().getRowGroup(8, 3).collapse(); is being called together with spreadsheet.getActiveSheet().expandAllRowGroups(); before the former finished executing.
This problem can be solved by implementing SpreadsheetApp.flush(); after each request that might be a bit time consuming when it is critical that it finishes before the next line of code will be executed.
So, e.g.
spreadsheet.getActiveSheet().expandAllRowGroups();
SpreadsheetApp.flush();
spreadsheet.getActiveSheet().getRowGroup(8, 3).collapse();
UPDATE:
If you want to dynamically collapse all row groups of a certain depth, you need to retrieve them first.
For this, you need to use the Advanced Sheets Service, which allows you to use the methods of the Sheets API in Apps Script.
After enabling the Advanced Sheets Service, you can collapse all groups with the desired depth as following:
function _3rdLevelofDetail() {
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.getActiveSheet().expandAllRowGroups();
var Id = spreadsheet.getId();
var groups = Sheets.Spreadsheets.get(Id, {
ranges: spreadsheet.getActiveSheet().getSheetName(),
fields: "sheets/rowGroups"
});
var rowGroups = groups.sheets[0].rowGroups;
for (var i = 0; i < rowGroups.length; i++){
if (rowGroups[i].depth == 3){
var index = rowGroups[i].range.startIndex;
spreadsheet.getActiveSheet().getRowGroup(index, 3).collapse();
}
}
};

Related

Creating New Spreadsheets from Spreadsheet

I am creating a sheet that can create a series of sheets that interact to help run a simulation. Part of this project involves creating a series of sheets- individual score sheets- that can only be accessed by single players in the game. The code I'm hoping to use is below, but I'm running into a series of issues. The first is thatit says I don't have permission to run SpreadsheetApp.create. I have spent about an hour looking around online for how to cure this, but just don't understand it and can't find a good answer. The second, and potentially broader issue, is that it seems incredibly complicated to use GAS to interact with different spreadsheets (i.e. not within the same spreadsheet). At a later point in the project, we will have to retrieve information from these sheets, etc. and if it is going to be difficult to do through scripts, may have to think of a workaround.
/** #OnlyCurrentDoc */
function individualSheets(){
var playerarray = playerArray();
var spreadsheet = SpreadsheetApp.getActive();
var spreadsheetlinks = []
//creates individual sheet for each player, then adds the link of their sheet to a list
for(i=0;i<playerarray.length;i++){
var spreadsheetname = playerarray[i];
var newspreadsheet = SpreadsheetApp.create(spreadsheetname);
spreadsheetlinks.push(DriveApp.getUrl(newspreadhseet));
}
//pasting the links of each player in the appropriate sheet
for(i=0;i<spreadsheetlinks.length;i++){
spreadsheet.setActiveSheet("PlayerInfo").getRange('D'+(i+1)).activate()
spreadsheet.getCurrentCell.setValue(spreadhseetlinks[i])
}
}
Apps Script doesn't have difficulty to use several Spreadsheets in the same script.
I would add another array to store the Ids, so you won't have to get them from the urls:
var spreadsheetlinks = []
var spreadsheetIds = [];
//creates individual sheet for each player, then adds the link of their sheet to a list
for(i=0;i<playerarray.length;i++){
var spreadsheetname = playerarray[i];
var newspreadsheet = SpreadsheetApp.create(spreadsheetname);
spreadsheetlinks.push(DriveApp.getUrl(newspreadhseet));
spreadsheetIds.push(newspreadsheet.getId);
}
You can open the Spreadsheets using openById, so you can use an unlimited amount of them:
var sprSheet1 = SpreadsheetApp.openById('id 1');
var sprSheet2 = SpreadsheetApp.openById('id 2');
var sprSheet3 = SpreadsheetApp.openById('id 3');
...
or even better:
var sprSheets = [];
for (var j = 0; j < spreadsheetIds.length; j++){
sprSheets.push(SpreadsheetApp.openById(spreadsheetIds[j]);
}
Regarding the SpreadsheetApp.create issue, make sure you authorized the needed scopes:
Scripts that use this method require authorization with one or more of
the following scopes:
https://www.googleapis.com/auth/spreadsheets.currentonly
https://www.googleapis.com/auth/spreadsheets
You can check them in Apps Script View > Manifest file. If you never touched this, they won't show up. You can add them manually:
"oauthScopes": [
"https://www.googleapis.com/auth/spreadsheets",
"https://www.googleapis.com/auth/drive.file"
]
In case you still get the permission issue when creating the Spreadsheet, you might want to check the playerArray function to see if it's actually returning a String.

Script to display board game matches based on criteria

My apologies if my wording is confusing, as I am new to scripting and programming. I have a board game list with my friends that have the list of games we own, the estimated time it takes to play the game, and the min/max players required to play the game. A wonderful member of the community created a script where I can input the amount of players I have, and the amount of time we have on our hands, and the script would list the available games we could lay base off of my input. Currently, I input the players and times on line two of the script ( var gamesThatFitCriteria = findGames(4,50); ). I would like the script to take the values ofdesignated two cells on the sheet to create these numbers.
Here is the sheet (editable for all, I have a separate private copy) with details on what I would like to accomplish, as well as my current script.
https://docs.google.com/spreadsheets/d/1AFTr_ji5iz8BJU9_OkZ1Xhfb9hg9ARAaRXfdVx2Y8pU/edit?usp=sharing
function runThis() {
var gamesThatFitCriteria = findGames(4,50);
var userInterface=HtmlService.createHtmlOutput(gamesThatFitCriteria.join('<br />'));
SpreadsheetApp.getUi().showModelessDialog(userInterface, "Games")
}
function findGames(player, timeInMinutes) {
var games=SpreadsheetApp.getActive();
var sh=games.getActiveSheet();
var rg=sh.getRange(3,1,sh.getLastRow(),sh.getLastColumn());
var values=rg.getValues();
var result = [];
values.forEach(function(r){var name=r[0];var time=r[2];var minPlayer=r[3];var maxPlayer=r[4];if(time<=timeInMinutes && player >= minPlayer && player <= maxPlayer) {result.push(name);}});
return result;
}
You want to retrieve the values from the cells B2:B3 on the sheet of Look Here.
You want to use the retrieved values to valueOfB2 and valueOfB3 of var gamesThatFitCriteria = findGames(valueOfB2, valueOfB3);.
You want to achieve this by modifying your script.
If my understanding is correct, how about this modification?
Modified script:
When your script is modified, please modify the function of runThis() as follows.
function runThis() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Look Here");
var [[valueOfB2], [valueOfB3]] = sheet.getRange("B2:B3").getValues();
var gamesThatFitCriteria = findGames(valueOfB2, valueOfB3);
var userInterface=HtmlService.createHtmlOutput(gamesThatFitCriteria.join('<br />'));
SpreadsheetApp.getUi().showModelessDialog(userInterface, "Games")
}
Note:
In your shared Spreadsheet, there are 2 functions of findGames in the files of Get Games Dialogue and On Open. In this case, one of 2 functions of findGames is run. So please be careful this.
In your script, when you run the function of runThis on the sheet of Games, the retrieved values are displayed. When you run the function on the sheet of Look Here, no values are displayed. Please be careful this.
If you want to display the values on the sheet of Look Here, please modify var sh=games.getActiveSheet(); to var sh=games.getSheetByName("Games");.
Reference:
getSheetByName()
If I misunderstood your question and this was not the result you want, I apologize.

Google apps script for sheets, how to create a 2-way link between two spreadsheets?

I'm attempting to write a script to continuously update two spreadsheets so that the information put in one always goes into the other.
The full project is to create a master spreadsheet with 50 people on it and each one of them has their own sheet/page, however they aren't allowed to access this master sheet as they are not allowed to see the other peoples data. (I am currently unaware of any viewing permission commands but I'm pretty sure there aren't any).
So the solution that I have been considering is creating 50 other spreadsheets and each of them will have a single sheet with all of the same information as the master Spreadsheet and the name of the sheet will be the same as on the master page.
I found this code on another post that was for a similar problem
function onEdit(e) {
// Get the event object properties
var range = e.range;
var value = e.value;
//Get the cell position
var row = range.getRowIndex();
var column = range.getColumnIndex();
exportValue(row,column,value)
}
function exportValue(row,column,value) {
var ss = SpreadsheetApp.openById(targetID);
var s = ss.getSheetByName(targetSheetName);
var target = s.getRange(row, column);
target.setValue(value);
}
in this I understand what everything is doing except the ("var range = e.range; var value = e.value;") lines, is ths what im looking for or not, idk
Thanks in advance
:)

Google sparesheet formula/function to get random values without recalculating(randbetween)

I am trying to achive the following. In google sparesheet I have one sheet with values "AllValues", in another sheet "Randomvalues" I would like to get random values from sheet "AllValues".
I have tried two options, first I tried randbetween formula:
=INDEX(AllValues!A4:A103,RANDBETWEEN(1,COUNTA(AllValues!A4:A103)),1)
It is working, but it refresh/recalculate new values all the time column is changed. Googeled a lot and seems that there is not much to do to freeze already calculated results.
Next I tried function:
function random() {
var sss = SpreadsheetApp.getActiveSpreadsheet();
var ss = sss.getSheetByName('Values'); //the sheet that has the data
var range = ss.getRange(1,1,ss.getLastRow(), 4); //the range you need: 4 columns on all row which are available
var data = range.getValues();
for(var i = 0; i < data.length; i++)
{
var j = Math.floor(Math.random()*(data[i].length)); //method of randomization
var element = data[i][j]; // The element which is randomizely choose
ss.getRange(i+1, 6).setValue(element);
}
}
But this function is not working for me, google sparesheet gives error on line 11, that setVaue is not allowed.
Line 11: ss.getRange(i+1, 6).setValue(element);
Googled this one too, there are lot of suggestion, but I am not very familiar with functions, I did not managed to get it working.
Hope that someone can help me out.
Using a formula assumes repeated calculations usually. You cannot prevent them and only can try to return old values instead. This task is not trivial, since any formula cannot refer to the same cell where the result is to be returned (a circular reference occurs). Do not use formulas for single time calculation.
On the other hand, using a script function makes it possible to generate required data directly and only once or on demand. I think, the function below will help you to understand all the neccesary steps for sample source and target ranges.
function random() {
var source = "AllValues!A4:A103",
target = "RandomValues!F2:F22";
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sourceValues = ss.getRange(source).getValues(),
targetRange = ss.getRange(target),
targetValues = [];
while (targetValues.length < targetRange.getHeight()) {
var randomIndex = Math.floor(Math.random() * sourceValues.length);
targetValues.push(sourceValues[randomIndex]);
}
targetRange.setValues(targetValues);
}
You can run it manually or choose a proper trigger.
There are multiple ways of achieving this goal.
Custom Menu
As mentioned by #Tanaike, you can avoid the recalculation and the formula dependency by using a Custom Menu:
// #OnlyCurrentDoc
// Create a function that binds the "simple trigger" for the open event:
function onOpen(e) {
// Add a menu to the UI with the function we want to be able to invoke.
const ui = SpreadsheetApp.getUi();
ui.createMenu("Randomizer")
.addItem("Sample from 'AllValues' sheet", "sampleAllValues")
.addToUi();
}
You then need a function definition matching this name sampleAllValues, and when the user selects the associated menu option, it will be invoked with the permissions of the clicking user (the user will be prompted first to provide consent for access per the script's OAuth scopes).
function sampleAllValues() {
const wb = SpreadsheetApp.getActive();
const destination = wb.getSheetByName("RandomValues");
const source = wb.getSheetByName("AllValues");
if (!source || !destination)
throw new Error("Missing required sheets 'RandomValues' and 'AllValues'");
// Create a flat array of all non-empty values in all rows and columns of the source sheet.
const data = source.getDataRange().getValues().reduce(function (compiled, row) {
var vals = row.filter(function (val) { return val !== ""; });
if (vals.length)
Array.prototype.push.apply(compiled, vals);
return compiled;
}, []);
// Sample the smaller of 50 elements or 10% of the data, without replacement.
const sample = [];
var sampleSize = Math.min(50, Math.floor(data.length * .1));
while (sampleSize-- > 0)
{
var choice = Math.floor(Math.random() * data.length);
Array.prototype.push.apply(sample, data.splice(choice, 1));
}
// If we have any samples collected, write them to the destination sheet.
if (sample.length)
{
destination.getDataRange().clearContent();
// Write a 2D column array.
destination.getRange(1, 1, sample.length, 1)
.setValues(sample.map(function (element) { return [ element ]; }));
// Write a 2D row array
// destination.getRange(1, 1, 1, sample.length)
// .setValues( [sample] );
}
}
Custom Function
If you still wanted to use a custom function from the RandomValues sheet, e.g.
RandomValues!A1: =sampleAllValues(50, AllValues!A1:A)
then you would need to return sample instead of write to a specific sheet. Note that custom functions are treated deterministically--they are computed at the time of entry and then only recalculated when the values of their arguments change. Custom functions run with very limited scope, so be sure to review their restrictions.
The above usage hints that you might find it useful to allow passing in the number of desired samples, and the values to sample from:
function sampleAllValues(sampleSize, value2Darray) {
const data = value2Darray.reduce(function (compiled, row) {
/* as above */
}, []);
/* sample as above */
return sample; // Must be 2D row or 2D column array, or a single primitive e.g. `1`
}
No matter which route you take, be sure to review your script's error logging by viewing your script's Stackdriver logs. (View -> Stackdriver Logging)
References:
Sheet#getRange
Custom functions
Custom menus
Array#reduce
Array#map
Array#splice
.push.apply

Google Sheets Create new tab

Need some advise on Google Sheets.
Sadly one of our clients uses Google Sheets as their Excel fix. As part of this one of the staff spends hours moving data about.
The first part of the problem is that we have a tab called master. In column A is a variable amount of cells (Some duplicates). We want to be able to create a new tab based on the distinct value in the cell (So one sheet per distinct value)
Now in Microsoft Excel VBA I can write this with my eyes closed, but on Google sheets I have no idea.
Any help is appreciated.
How about:
function createNewSheets() {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var masterSheet = spreadsheet.getSheetByName('master');
// Retrieve 2d array for column A
var colA = masterSheet.getRange('A:A').getValues();
// Create a 1d array of unique values
var uniqueValues = {};
colA.forEach(function(row) {
row[0] ? uniqueValues[row[0]] = true : null;
});
var newSheetNames = Object.keys(uniqueValues);
newSheetNames.forEach(function(sheetName) {
// Check to see whether the sheet already exists
var sheet = spreadsheet.getSheetByName(sheetName);
if (!sheet) {
spreadsheet.insertSheet(sheetName);
}
});
}