Hiding/unhiding named ranges: Improve script process time? - google-apps-script

Below is a script I'm using to automate the hiding/unhiding of groups of rows in Google Sheets.
Col B contains drop-down menus that allow the user to toggle hiding or unhiding rows according to named ranges. However, the script is a bit laggy. Here is a sample spreadsheet where you can see for yourself.
I haven't found a more efficient approach than creating four different arrays in Google Apps Script...but any advice on how to speed up this script would be greatly appreciated!
(NB: The script operates with a "From spreadsheet" and "On edit" trigger.)
function hideRows() {
var ss = SpreadsheetApp.getActive();
var sheet = ss.getSheetByName("Sheet");
//create array of spreadsheet's named ranges
var namedRanges = ss.getNamedRanges();
var named = [];
for (var i = 0; i < namedRanges.length; i++) {
named.push(namedRanges[i].getName());
}
var arrName = named.sort();
//create array of named range values
var value = [];
for (var i = 0; i < arrName.length; i++) {
value.push(ss.getRangeByName(arrName[i]).getValue());
}
//create array of named range row indexes
var index = [];
for (var i = 0; i < arrName.length; i++) {
index.push(ss.getRangeByName(arrName[i]).getRowIndex());
}
//create array of named range row numbers
var rows = [];
for (var i = 0; i < arrName.length; i++) {
rows.push(ss.getRangeByName(arrName[i]).getNumRows());
}
for (var i = 0; i < arrName.length; i++) {
//If a value in value is equal to "Collapsed" then hide the corresponding named range
if (value[i] == "Collapsed") {
sheet.hideRows(index[i+1],rows[i+1]);
}
//If a value in value is equal to "Expanded" then show the corresponding named range
else if (value[i] == "Expanded") {
sheet.showRows(index[i+1],rows[i+1]);
}
}
}

I didn't test this. I rewrote the code, and consolidated three of the loops into one loop. The code could be consolidated more, depending upon whether you need to further use the data for something else. For example, you are sorting the range names. If it's not really needed to have them in order, then the code could be consolidated further.
function HideRows() {
var arrName,i,index,L,named,namedRanges,
rngByName,rngNumRows,rngRowIndex,rngVal,rows,
sheet,ss,thisRng,thisRngName,value;
ss = SpreadsheetApp.getActive();
sheet = ss.getSheetByName("Sheet");
named = [];
value = [];
index = [];
rows = [];
//create array of spreadsheet's named ranges
namedRanges = ss.getNamedRanges();
L = namedRanges.length;
for (i = 0; i < L; i++) {
named.push(namedRanges[i].getName());
}
arrName = named.sort();
Logger.log(arrName);
//create array of named range values
L = named.length;
for (i = 0; i < L; i++) {
thisRngName = arrName[i];
thisRng = ss.getRangeByName(thisRngName);
rngVal = thisRng.getValue();
rngRowIndex = thisRng.getRowIndex();
rngNumRows = thisRng.getNumRows();
value.push(rngVal);
index.push(rngRowIndex);//create array of named range row indexes
rows.push(rngNumRows);//create array of named range row numbers
//If a value in arrValue is equal to "Collapsed" then hide the corresponding named range
if (rngVal == "Collapsed") {
sheet.hideRows(index[i+1],rows [i+1]);
}
//If a value in arrValue is equal to "Expanded" then show the corresponding named range
else if (rngVal == "Expanded") {
sheet.showRows(index[i+1],rows [i+1]);
}
}
}

Related

Google Script function to update Google Form Dropdowns populated by cell values in a Google Sheet

The following script will create a form with 4 sections. The first section of the form lists three options (dropdown) and depending on which one you choose, it will jump to the relevant section to request more information about that particular option (more dropdowns).
I am populating the first section with the names of three sheets (sql_db_info, oracle_db_info, imanis_db_info)
I am populating the three different dependant sub sections using the rows in the first two columns of each sheet.
All of this works.
The problem I have is what I need to do to update the different sections if I change any values in the columns of the existing sheets or if I add new sheets.
If I add more sheets (mongo_db_info, casandra_db_info) and their associated dependent sub sections (using the values in the new sheets), it appends to the existing form (adding more sections).
I tried to delete and recreate the sections each time but that screws up the collection of the responses in the relevant sheet. New columns are created and it spreads out of control.
So....I need to be able to just update the relevant sections by adding new entries and removing deleted entries or by overwriting the dropdown entries without recreating the sections.
So, the looping has to work in case I add new sheets (new database type with new dependent sections) but recreating the Form each time wont cut it.
So. Additional sheets, will add to the additional first section (sql, oracle, imanis, mongo, cassandra etc).
And the cell values in the first two columns of those sheets will provide the dropdowns for the subsequent two sections for each option chosen in the first section (sql,oracl,imanis etc).
How can I update the relevant dropdowns when I add new sheets or add/delete values from the columns in the sheets?
In the below code, the dbMaker function works on the first run.
What do I need to do to get it to just update dropdowns on subsequent runs?
var ssID = "url id is put here";
var formID = "url id is put here"
var ss = SpreadsheetApp.openById(ssID);
var form = FormApp.openById((formID));
//function clearAll(form){
// var form = FormApp.openById((formID));
// var items=form.getItems();
// Logger.log(items);
// //if(items < 1 ) {
// var d=0;//deleted items counter
// for (var i=0; i<items.length; i++) {
// form.deleteItem(i-d++);
//
// }
//}
// dbMaker();
//}
function dbMaker() {
//var message = 'The current time is ' + new Date().toString();
//Logger.log(message);
var sheets = ss.getSheets().filter(function(sheet) {return sheet.getName().match(/db_info/gi);});
var dbSelect = form.addListItem().setTitle('DB Type').setRequired(true);
var dbChoices = [];
for(var i = 0; i < sheets.length; i++) {
var dbName = sheets[i].getName();
var dbSection = form.addPageBreakItem().setTitle(dbName).setGoToPage(FormApp.PageNavigationType.SUBMIT);
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
var nativetech = getnativetech(sheets[i]);
var nativetech_Select = form.addListItem().setTitle('Native Technology' + ' Choice').setHelpText('Select the correct DB type').setRequired(true);
var nativetech_Choices = [];
for(var j = 0; j < nativetech.length; j++) {
nativetech_Choices.push(nativetech_Select.createChoice(nativetech[j]));
Logger.log('nativetech choices', nativetech[j]);
}
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
var integratedtech = getintegratedtech(sheets[i]);
var integratedtech_Select = form.addListItem().setTitle('Intregrated with company' + ' Choice').setHelpText('Select the correct option').setRequired(true);
var integratedtech_Choices = [];
for(var k = 0; k < integratedtech.length; k++) {
integratedtech_Choices.push(integratedtech_Select.createChoice(integratedtech[k]));
Logger.log('integratedtech choices', integratedtech[k]);
}
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
nativetech_Select.setChoices(nativetech_Choices).setRequired(true);
integratedtech_Select.setChoices(integratedtech_Choices).setRequired(true)
dbChoices.push(dbSelect.createChoice(dbName, dbSection));
}
dbSelect.setChoices(dbChoices).setRequired(true);
}
function getnativetech(sheet) {
var result1 = ""
var nativetech_Values = sheet.getDataRange().getValues();
var result1 = nativetech_Values.reduce(function(ar, e) {
if (e[0]) ar.push(e[0])
return ar;
}, []);
var nativetech = [];
for(var i = 1; i < result1.length; i++) {
Logger.log('result1',result1[i]);
nativetech.push(nativetech_Values[i][0]);
}
return nativetech;
}
function getintegratedtech(sheet) {
var result2 = ""
var integratedtech_Values = sheet.getDataRange().getValues();
var result2 = integratedtech_Values.reduce(function(ar, e) {
if (e[1]) ar.push(e[1])
return ar;
}, []);
var integratedtech = [];
for(var i = 1; i < result2.length; i++) {
Logger.log('result2',result2[i]);
integratedtech.push(integratedtech_Values[i][1]);
}
return integratedtech;
}

Clear content insted of deleting row

I have a range with data where I need a script to look for duplicates in the first column, and clear contents of the hole row in that range.
From this
To this
I've found this script, but it deletes the whole row. I need it to only clear content.
function ClearDuplicates() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("03");
var rows = sheet.getLastRow();
var firstColumn = sheet.getRange("A:A").getValues();
for (var i = rows; i >= 2; i--) {
if (firstColumn[i-1][0] == firstColumn[i-2][0]) {
sheet.deleteRow(i);
}
}
}
You need to retrieve the array data from column A and iterate through it comparing with the same array. When a match happens, apply clearContent() function [1] to clear the contents on that range (First get the range you want to clear in that row).
function ClearDuplicates() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1");
var rows = sheet.getLastRow();
var firstColumnValues = sheet.getRange("A1:A" + rows).getValues();
for (var i = 0; i < firstColumnValues.length; i++) {
var value = firstColumnValues[i][0];
for (var j = 0; j < firstColumnValues.length; j++) {
var comparisonValue = firstColumnValues[j][0];
if ((value == comparisonValue) && (j>i)) {
//You need to change the number of columns to which you need to clear the contents, in this case is 10
sheet.getRange(j+1, 1, 1, 10).clearContent();
}
}
}
}
[1] https://developers.google.com/apps-script/reference/spreadsheet/range#clearcontent
You can just grab the range and clear it instead as seen below:
sheet.getRange("R"+i+"C").clear();

Google script - Exceeded maximum execution time , help optimize

google script spreadsheet
Novice
I try to create a matrix , if the array is a small database everything works fine, of course if it exceeds 800 lines and more rests on the error "You have exceeded the maximum allowed run time ." Not effectively create a matrix :
var s = SpreadsheetApp.getActiveSheet(); //List
var toAddArray = []; //Greate Arr
for (i = 1; i <= s.getLastRow()+1; ++i){ //Start getting Value
var numbr = s.getRange(i,4); //detect range
var Valus = numbr.getValues().toString(); //get value
//filter value
var newznach = Valus.replace(/\-/g, "").replace(/[0-9][0-9][0-9][0-9][0-9][a-zA-Zа-яА-Я][a-zA-Zа-яА-Я]/g, "").replace(/[a-zA-Zа-яА-Я][a-zA-Zа-яА-Я]/g, "");
toAddArray.push([i.toFixed(0),Valus,newznach]); //add to array 0- Row numb, 1- Value, 2- "filtered" value
}
toAddArray =
{
Row, Value, NewValue - filtered
Row, Value, NewValue - filtered
Row, Value, NewValue - filtered
...
}
Can I somehow get an array of the same the other way ( faster, easier ) ?
You're doing a call to getValues every row, that eats a lot of performance.
It is better to do one big call to have all the data and then go through it sequentially.
var s = SpreadsheetApp.getActiveSheet();
var data = s.getRange(1,4, s.getLastRow()).getValues();
var toAddArray = data.map(function(row, i) {
var Valus = row[0].toString();
var newznach = Valus.
replace(/\-/g, "").
replace(/[0-9][0-9][0-9][0-9][0-9][a-zA-Zа-яА-Я][a-zA-Zа-яА-Я]/g, "").
replace(/[a-zA-Zа-яА-Я][a-zA-Zа-яА-Я]/g, "");
return [i.toFixed(0), Valus, newznach];
});
this code:
var Valus = numbr.getValues().toString();
slows you down because you read data from the sheet in a loop.
Try reading data once into array and then work with it:
var data = s.getDataRange().getValues();
And then work with data, in a loop. This sample code log each cell in active sheet:
function logEachCell() {
var s = SpreadsheetApp.getActiveSheet();
var data = s.getDataRange().getValues();
// loop each cell
var row = [];
for (var i = 0; i < data.length; i++) {
row = data[i];
for (var j = 0; j < row.length; j++) {
Logger.log(row[j])
}
}
}

Simple conditional formatting in Google Sheets Script Editor

I am trying to create some basic conditional formatting rules with Scripts instead of the conditional formatting editor in sheets, because when a new row is added or removed, it breaks the rules already set. For example, when a row is deleted, the condition may be for the range A:C but then it adds "A1:C5,A6:C898". This causes some rows to be skipped by the rules so I am hoping a script will solve this.
So I want to simply change the cell background to green if the cell text is exactly "Y". I want to change it to red if text is exactly "N". I have other rules that I want to use but I am having trouble with the basics on this.
What I have so far in my script:
function onEdit(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("PRECLOSING");
var range = sheet.getRange("E:E,I:J,M:M,P:P,S:V,AB:AC,AF:AF");
range.activate();
var values = rangeY.getValues();
//for each row that data is present
for(var i = 0; i < values.length; i++) {
var cell = sheet.getRange(i + 1, 2);
if ( values == "X" )
{
cell.setBackground('black');
return;
} else {
cell.setBackground('white');
}
}
}
I made some modifications to your code.
The function getRange i don't think it can accept the ranges in that way, instead i think it can get just ranges that are continue (eg. A1:E10)
For that I created an array with the ranges you need and then I loop through them.
The function "getValues" returns a two dimensional array so you will need a second loop to get the actual value from the cell.
Then as you already have the range, you need to get the cell inside that range and not a new complete range.
function onEdit(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("PRECLOSING");;
var columns = ['E:E','I:J','M:M','P:P','S:V','AB:AC','AF:AF'];
for(var col in columns){ //loop to iterate the desired ranges
var range = sheet.getRange(columns[col]);
range.activate();
var values = range.getValues();
//for each row that data is present
for(var i = 0; i < values.length; i++) { //loop to iterate the rows
for( var j = 0; j< values[i].length ; j++){ //loop to iterate the columns
var cell = range.getCell(i+1, j+1);
if ( values[i][j] == "X" )
{
cell.setBackground('black');
}
}
}
}
}
Hope this helps.

sorting columns independently at array level

I have a spreadsheet with several columns that I need to sort individually.
I wrote the script below that works but is a bit slow since it handles each column in turn with getValues() and setValues().
I'd like to find a way to do the whole sorting at an array level for more efficiency but I don't know how... any suggestion ?
Here is the relevant part of the code I use now :
...
sh3.getRange(1,1,1,newData[0].length).setFontWeight('bold');// newData is an array corresponding to the whole sheet
for(col=1;col<newData[0].length;++col){
var val = sh3.getRange(2,col,getLastRowInCol(col),1).getValues().sort();// each column have a different height
sh3.getRange(2,col,getLastRowInCol(col),1).setValues(val)
}
}
function getLastRowInCol(col){
var values = sh3.getRange(2,col,sh3.getLastRow(),1).getValues();// skip header and find last non empty row in column
for(n=0;n<values.length;++n){
if(values[n]==''){break}
}
return n
}
Note : I know there is a Library by Romain Vialard that does the job (sorting columns in 2D arrays) but I'm interrested on how to do it 'manually' for personal JS skills improvement ;-) and also I need to sort every column independently without needing to update the sheet for every column.
How about:
function sortColumns() {
var sheet = SpreadsheetApp.getActiveSheet();
var startRow = 2;
var startCol = 1;
var dataRange = sheet.getRange(startRow, startCol, sheet.getLastRow() - startRow + 1, sheet.getLastColumn() - startCol + 1);
var data = dataRange.getValues();
// transpose data so each column item will be listed in an single array
// for each column so that it can be sorted with array.sort()
var rowToCol = [];
for (var i = 0; i < data[0].length; i++) {
rowToCol.push([]);
for (var j = 0; j < data.length; j++) {
// replace empty string with undefined as undefined sorts last
rowToCol[i].push(data[j][i]==""?undefined:data[j][i]);
}
rowToCol[i].sort();
// default sort, as above, is alphabetic ascending. For other methods
// search for Javascript array sort functions
}
// transpose sorted items back to their original shape
var result = [];
for (var i = 0; i < rowToCol[0].length; i++) {
result.push([]);
for (var j = 0; j < rowToCol.length; j++) {
result[i].push(rowToCol[j][i]==undefined?"":rowToCol[j][i]);
}
}
dataRange.setValues(result);
};
function onOpen() {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var entries = [{
name : "Sort Columns",
functionName : "sortColumns"
}];
sheet.addMenu("Script Center Menu", entries);
};