Google Apps Script Replace and update cell within a range - arrays

I have a Google spreadsheet that I'm trying to remove the word "woo" within a range of cells
So far I've managed to loop through the results and log the results, however I haven't figured how to update that information in the spreadsheet itself.
Any guidance would be welcomed
Thank you
function myFunction () {
var ss = SpreadsheetApp.getActiveSheet().getRange('B:B')
var data = ss.getValues();
for (var i = 0; i < data.length; i++) {
var text = data[i].toString();
var finaltext = text.replace(/woo/g, "");
data[i] = finaltext;
Logger.log(data[i]);
}
}

Use setValues()
Notes:
Usually ss is used as a shorthand for spreadsheet, as it's used on the code for a range it's better to use range as a variable name.
setValues() returns a 2D array, so data[i] returns an array of row values rather than a cell value. To get/set cell values, use data[i][0] notation.
Considering the above replace
var ss = SpreadsheetApp.getActiveSheet().getRange('B:B')
by
var range = SpreadsheetApp.getActiveSheet().getRange('B:B')
then add the following line after the for block.
range.setValues(data);
Regarding text var declaration, replace
var text = data[i].toString();
to
var text = data[i][0].toString();
Using open ended references like B:B could lead to problems. To avoid them be sure to keep the sheet rows at minimum or better instead of using an open ended reference use something like B1:B10.

Related

Loop through range to find matching string google scripts

I'm trying to loop through the top title row of my spreadsheet to find the index number of a column based on the title name so that if someone inserts a column my code won't break. Here's what I have so far:
var sheet = SpreadsheetApp.getActive().getSheetByName('RawData');
var values = sheet.getRange(1,1,sheet.getMaxRows(),sheet.getMaxColumns()).getValue(); //line that's breaking
range.setNote("inside function");
var i = 1;
while(values[1][i] != name) {
i++;
}return i;
}
The code appears to break on the line where I set 'values,' and I can't figure out how to access the array I created in order to check which one contains the same string as the 'name' parameter. The setNote line is just for testing purposes, you can ignore it.
Thanks in advance.
EDIT
function getColumnByName() {
var name = "Ready For Testing?";
var ss=SpreadsheetApp.getActive().getSheetByName('RawData');
var vA=ss.getRange(1,1,ss.getLastRow(),ss.getLastColumn()).getValues();
var hA=vA.shift();//returns header array vA still contains the data
var idx={};
var index = hA.forEach(function(name,i){idx[name]=i});//idx['header name'] returns index
return index;
}
I set name just for testing purposes, it will be passed as a parameter when I use the actual function
Try
function getColumnByName() {
var name = "Ready For Testing?";
var ss=SpreadsheetApp.getActive().getSheetByName('RawData');
var vA=ss.getDataRange().getValues();
var hA=vA.shift();//returns header array vA still contains the data
var idx= hA.indexOf(name);
if(idx === -1 ) throw new Error('Name ' + name + ' was not found');
return idx + 1; // Returns the name position
}
Instead of
var values = sheet.getRange(1,1,sheet.getMaxRows(),sheet.getMaxColumns()).getValue();
and instead of
var vA=ss.getRange(1,1,ss.getLastRow(),ss.getLastColumn()).getValues();
use
var values = sheet.getDataRange().getValues();
getValue() only returns the value of top-left cell
using sheet.getMaxRows() / sheet.getMaxColumns() returns the sheet last row / column which could cause getting a lot of blank rows / columns.
compared with the second line code (getLastRow() / getLastColumn()) the proposed code line is "cheaper" (one call to the Spreadsheet Service instead of three (1. getRange, 2. getLastRow, 3 getLastColumn Vs. getDataRange) to get the data range and usually is faster too.
Regarding
var index = hA.forEach(function(name,i){idx[name]=i});
forEach returns undefined ref. Array.prototype.forEach
Try this:
function myFunction() {
var ss=SpreadsheetApp.getActive()
var sh=ss.getSheetByName('RawData');
var vA=sh.getRange(1,1,sh.getLastRow(),sh.getLastColumn()).getValues();
var hA=vA.shift();//returns header array vA still contains the data
var idx={};
hA.forEach(function(h,i){idx[h]=i});//idx['header name'] returns index
var end="is near";//set a break point here and run it down to here in the debugger and you can see hA and idx
}
hA is the header name array
idx is an object that returns the index for a given header name
idx is all you need just learn to put these three lines of code in your script in the future and you won't need to use an external function call to get the index.
var hA=vA.shift();//returns header array vA still contains the data
var idx={};
hA.forEach(function(h,i){idx[h]=i});//idx['header name'] returns index
vA still contains all of your data

Apps Script (Google Sheets) How to perform .setHiddenValues with an Array

I have some values stored in an Array and now I want to remove ALL the values stored in this array from a filter.
The values are stored correctly in the array but I don't manage to remove the values from the filter.
The Array's name is HideValues
Here is some code:
var p = 0;
spreadsheet.getSheetByName('TEM Tool Data').getRange('\'TEM Tool Data\'!E1').activate();
var criteria = SpreadsheetApp.newFilterCriteria();
//Remove all PID´s from the filter
while (p < HideValues.length){
criteria.setHiddenValues([HideValues[p]]).build();
p++;}
//Filter
spreadsheet.getSheetByName('TEM Tool Data').getFilter().setColumnFilterCriteria(5, criteria);
//Copy filtered area
spreadsheet.getRange('A2:I1386').activate();
//Paste
spreadsheet.getSheetByName('Visualization').getRange('A5').activate();
spreadsheet.getRange('\'TEM Tool Data\'!A2:I1386').copyTo(SpreadsheetApp.getActiveRange(),
SpreadsheetApp.CopyPasteType.PASTE_NORMAL, false);
spreadsheet.getSheetByName('Visualization').getRange('J3').activate();
};
You don't need to loop through the array
If your array is something like var HideValues = [1,2,3,4,5];,
you can simply specify criteria.setHiddenValues(HideValues).build(); - without the while loop
Also:
To a filter, you should create it first (if not already done) and apply it to a range, not sheet:
var filter = spreadsheet.getSheetByName('TEM Tool Data').getDataRange().createFilter();
filter.setColumnFilterCriteria(3, criteria);
You should apply it to a range, not a sheet

In a form, I need to populate the contents of same spreadsheet column like 50 times. Is there a way to script this logic with fewer iterations

I'm working on a form where I need to pull the contents of a spreadsheet column like 50 times, to try to input multiple items from a list. I see that I can do this by defining a few variables and redoing a small piece of Script again and again. I want to see if anyone can help me overcome this lengthy script to make it smaller with fewer iterations. Thanks.
function updateForm(){
// call the form and connect to the drop-down items
var Form_SQ = FormApp.openById("FORM ID");
var SQ_IT01_List = Form_SQ.getItemById("ITEM 01").asListItem();
var SQ_IT02_List = Form_SQ.getItemById("ITEM 02").asListItem();
//Similarly defining upto 50 dropdown lists.
var SS01 = SpreadsheetApp.getActive();
var SQ_IT01_Names = SS01.getSheetByName("Sheet2");
var SQ_IT02_Names = SS01.getSheetByName("Sheet2");
//Similarly defining upto 50 names lists.
// Item_01 Part Number Dropdown
var SQ_IT01_Values = SQ_IT01_Names.getRange(2, 1, SQ_IT01_Names.getMaxRows() - 1).getValues();
var SQ_IT01_Items = [];
for(var i = 0; i < SQ_IT01_Values.length; i++)
if(SQ_IT01_Values[i][0] != "")
SQ_IT01_Items[i] = SQ_IT01_Values[i][0];
SQ_IT01_List.setChoiceValues(SQ_IT01_Items);
// Item_02 Part Number Dropdown
var SQ_IT02_Values = SQ_IT01_Names.getRange(2, 1, SQ_IT02_Names.getMaxRows() - 1).getValues();
var SQ_IT02_Items = [];
for(var i = 0; i < SQ_IT02_Values.length; i++)
if(SQ_IT02_Values[i][0] != "")
SQ_IT02_Items[i] = SQ_IT02_Values[i][0];
SQ_IT02_List.setChoiceValues(SQ_IT02_Items);
//Similarly defining upto 50 lookup lists.
}
Problem
Reusing code and making use of loops. Scripting is all about efficiency (see DRY principle): make as little assignments and same-functionality coding as possible - use loops, move reusable code snippets to functions that can be called on demand, etc.
Solution
This sample makes several assumptions:
SQ_IT01_Names is different for each item (in your sample it always is Sheet2 - if this is the case, you don't have to reassign it 50 times, one variable assignment will do just fine).
You intended to do something when a value is an empty string (the sample just filters them out). As you use the [index] notation, those values in the resulting Array will be undefined (and that's not something one would want in an Array of choice values).
All items are choice items (if you need id filtering, the sample is easily expanded).
function updateForm() {
var form = FormApp.openById("FORM ID");
//access every item;
var items = form.getItems();
var ss = SpreadsheetApp.getActive();
//loop over items;
items.forEach(function(item,i){
var namesSheet = ss.getSheetByName('Sheet'+i); //assuming this is diff each time;
var namesRange = namesSheet.getRange(2,1,namesSheet.getLastRow());
var namesValues = namesRange.getValues();
//map values to first column;
namesValues = namesValues.map(function(value){
return value[0];
});
//filter out undefined (undefined and false functional equivalence);
namesValues = namesValues.filter(function(value){
return value;
});
item.asListItem().setChoiceValues(namesValues);
});
}
Notes
Please, use closures {} with loops and if statements, this way you'll be able to keep track of which statements are enclosed in it and save yourself debugging time when looping / conditioning multiple statements.
Since you only need rows that have data in them, use the getLastRow() method instead of the getMaxRows()-1 calc you have to perform in your script.
Reference
forEach() method reference;
filter() method reference;
map() method reference;
getLastRow() method reference;

setFormulas and rangeList

I'm trying to setFormulas over a range of non-contiguous cells. I need a formula (they're all different) set every 30 cells in a single column (c).
It works to setFormula for each cell, but creating 56 variables seems unnecessary. I can get the formulas but not set them as intended. I also tried using getRangeList but I'm not sure that does what I think it does. Any advice?
function test() {
var spreadsheetU09U10 = SpreadsheetApp.openById('some url');
var sheetU09 = spreadsheetU09U10.getSheetByName('TEST');
var sheetU10 = spreadsheetU09U10.getSheetByName('U10');
var sheetDATA = spreadsheetU09U10.getSheetByName('Sheet4');
//U09 SHEET
//var rangeListU09 = sheetU09.getRangeList(['C4','C34','C64','C94','C124','C154','C184','C204','C234','C264','C294','C324','C354','C384','C404','C434','C464','C494',
//'C524','C554','C584','C604','C634','C664','C694','C724','C754','C784']);
//Logger.log(rangeListU09);
var startRow = 4;
var startColumn = 3;
var numRows = sheetU09.getLastRow();
var numColumns = 1;
var range = sheetU09.getRange(startRow, startColumn, numRows, numColumns);
var getFormulasU09 = sheetDATA.getRange('C30:C57').getFormulas();
//Logger.log(getFormulasU09);
Logger.log(getFormulasU09.length);
for (var i = 0; i < getFormulasU09.length; i++) {
var setFormulasU09 = range.setFormulas(getFormulasU09);
Logger.log(setFormulasU09);
startRow = startRow + 29;
}
It isn't clear exactly where the formulas you are using are originating from, but the RangeList class can help reduce the read time, even if you use it just to call getRanges. If the formula is the same in R1C1 format, then you can very effectively use RangeList#setFormulaR1C1.
Assuming you have formulas in one region that must be written verbatim in a disjoint set of cells:
const wb = SpreadsheetApp.getActive();
// Assuming only text formulas, not actual "entered" formulas
const formulas = wb.getSheetByName("formulas").getDataRange()
.getValues()
.map(function (row) { return row[0]; });
const sheet = wb.getSheetByName("some name");
const destinations = [
// Depending on the relationship between destinations, one could programmatically generate these
];
// Efficiently acquire references to multiple disjoint Ranges
const rl = sheet.getRangeList(destinations);
// Assume the i-th formula goes in the i-th range
rl.getRanges().forEach(function (rg, i) {
rg.setFormula(formulas[i]);
});
// The RangeList makes uniformly formatting these disjoint ranges extremely simple
rl.setFontWeight('bold');
...
Reference
- RangeList
You want to put formulas to the individual cells.
You want to put 28 formulas to cells of ['C4','C34','C64','C94','C124','C154','C184','C204','C234','C264','C294','C324','C354','C384','C404','C434','C464','C494', 'C524','C554','C584','C604','C634','C664','C694','C724','C754','C784'] in the sheet of TEST.
If my understanding is correct, how about using values.batchUpdate of Sheets API? The flow of this script is as follows.
Set range list as 1 dimensional array.
Retrieve formulas.
Create request body for sheets.spreadsheets.values.batchUpdate.
In order to use this script, please enable Sheets API at Advanced Google Services and API console. You can see about how to enable Sheets API at here.
Sample script:
function test() {
var spreadsheetId = "### spreadsheetId ###"; // Please set this.
var sheetName = "TEST";
var spreadsheetU09U10 = SpreadsheetApp.openById(spreadsheetId);
var sheetU09 = spreadsheetU09U10.getSheetByName(sheetName);
// var sheetU10 = spreadsheetU09U10.getSheetByName('U10'); // This is not used in this script.
var sheetDATA = spreadsheetU09U10.getSheetByName('Sheet4');
var rangeListU09 = ['C4','C34','C64','C94','C124','C154','C184','C204','C234','C264','C294','C324','C354','C384','C404','C434','C464','C494', 'C524','C554','C584','C604','C634','C664','C694','C724','C754','C784'];
var getFormulasU09 = sheetDATA.getRange('C30:C57').getFormulas();
rangeListU09 = rangeListU09.map(function(e) {return sheetName + "!" + e});
var resource = {
data: rangeListU09.map(function(e, i) {return {range: e, values: [[getFormulasU09[i][0]]]}}),
valueInputOption: "USER_ENTERED",
};
Sheets.Spreadsheets.Values.batchUpdate(resource, spreadsheetId);
}
Note:
From your question, I'm not sure about the detail formulas. If the a1Notation of each formulas is required to be modified, can you provide a sample spreadsheet including the formulas?
Reference:
sheets.spreadsheets.values.batchUpdate
If I misunderstand your question, please tell me. I would like to modify it.
I'm assuming that you want to copy the whole column starting from the cell locations in the array. That wasn't really clear to me.
function test109() {
var ss=SpreadsheetApp.getActive();
var shU09=ss.getSheetByName('35');//formulas get copied into here starting at row 4
var shDATA=ss.getSheetByName('36');//formulas stored in here C30:C57
var fA=shDATA.getRange('C30:C57').getFormulas();
var dA=['C4','C34','C64','C94','C124','C154','C184','C204','C234','C264','C294','C324','C354','C384','C404','C434','C464','C494','C524','C554','C584','C604','C634','C664','C694','C724','C754','C784'];
for(var i=0;i<dA.length;i++){
var rgs=Utilities.formatString('%s:%s',dA[i],shU09.getRange(dA[i]).offset(fA.length-1,0).getA1Notation());//this uses range.offset to calculate the correct range in A1Notation.
shU09.getRange(rgs).setFormulas(fA);
}
}
As it turns out I just noticed that there are 28 locations and 28 formulas. Perhaps that was intentional and you want to copy a different formula in each location then this version would do that.
function test109() {
var ss=SpreadsheetApp.getActive();
var shU09=ss.getSheetByName('35');//formulas get copied into here starting at row 4
var shDATA=ss.getSheetByName('36');//formulas stored in here C30:C57
var fA=shDATA.getRange('C30:C57').getFormulas();
var dA=['C4','C34','C64','C94','C124','C154','C184','C204','C234','C264','C294','C324','C354','C384','C404','C434','C464','C494','C524','C554','C584','C604','C634','C664','C694','C724','C754','C784'];
for(var i=0;i<dA.length;i++){
shU09.getRange(dA[i]).setFormula(fA[i][0]);
}
}
Range Offset

Store formatting information in an array then apply it to a range

I'm trying to create a script that will automatically format a selection based on the formatting of a table in another sheet. The idea is that a user can define a table style for header, rowOdd and rowEven in the Formats sheet, then easily apply it to a selected table using the script.
I've managed to get it working, but only by applying one type of formatting (background colour).
I based my code for reading the code into an array on this article.
As you will hopefully see from my code below, I am only able to read one formatting property into my array.
What I would like to do is read all formatting properties into the array, then apply them to the range in one go. I'm new to this so sorry if my code is a mess!
function formatTable() {
var activeRange = SpreadsheetApp.getActiveSpreadsheet().getActiveRange(); //range to apply formatting to
var arr = new Array(activeRange.getNumRows());
var tableStyleSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Formats"); //location of source styles
var tableColours = {
header: tableStyleSheet.getRange(1, 1, 1).getBackground(),
rowEven: tableStyleSheet.getRange(2, 1, 1).getBackground(),
rowOdd: tableStyleSheet.getRange(3, 1, 1).getBackground()
}
for (var x = 0; x < activeRange.getNumRows(); x++) {
arr[x] = new Array(activeRange.getNumColumns());
for (var y = 0; y < activeRange.getNumColumns(); y++) {
x == 0 ? arr[x][y] = tableColours.header :
x % 2 < 1 ? arr[x][y] = tableColours.rowOdd : arr[x][y] = tableColours.rowEven;
Logger.log(arr);
}
}
activeRange.setBackgrounds(arr);
}
Thanks!
I might be wrong but based from the list of methods given in Class Range, feature to save or store formatting details currently do not exist yet.
However, you may want to try using the following:
copyFormatToRange(gridId, column, columnEnd, row, rowEnd) or copyFormatToRange(sheet, column, columnEnd, row, rowEnd) wherein it copies the formatting of the range to the given location.
moveTo(target) wherein it cuts and paste (both format and values) from this range to the target range.
Did you know that you can get all of the different formatting elements for a range straight into an array?
E.g.
var backgrounds = sheet.getRange("A1:D50").getBackgrounds();
var fonts = sheet.getRange("A1:D50").getFontFamilies();
var fontcolors = sheet.getRange("A1:D50").getFontColors();
etc.
However, there's no way to get all of the formatting in one call unfortunately, so you have to handle each element separately. Then you can apply all of the formats in one go:
targetRng.setFontColors(fontcolors);
targetRng.setBackgrounds(backgrounds);
and so on.

Resources