Printing Unspecified Number of Array Contents in Google Sheets Cells (GAS) - javascript

I am collecting information from a database, based on a filter and is stored in an array of arrays. Once this information is collected I want to be able to print this data onto a new sheet like this:
___________________________________
|*****|John Buck, Associate, Luxura|
------------------------------------
|*****|Jane Doe, Manager, Eclipse |
------------------------------------
|*****|Jim Bob, Executive, Lokia |
------------------------------------
|*****|Suzzy Sue, Director, RoomX |
___________________________________
The most important part is that each time I run this function the array may be a different size, so I need it to continue through the array until all records have been printed down the sheet. I've tried a few things, but I am only used to using forEach or for loops when there is a condition, where as this one would just print the specified info in every array.
function testArray() {
var activeSpreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var activeSheet = activeSpreadsheet.getActiveSheet();
var activeRange = activeSpreadsheet.getActiveRange();
var activeCell = activeSpreadsheet.getActiveCell();
var startCell = activeSheet.getRange(5, 4);
var ratingStartCell = activeSheet.getRange(5, 3);
var newArray = [
["John Buck", "*****", "Associate", "Luxura"][
("Jane Doe", "****", "Manager", "Eclipse")
][("Jim Bob", "***", "Executive", "lokia")][
("Suzzy Sue", "*****", "Director", "RoomX")
],
];
newArray.forEach((r, o) => {
startCell.setValues(
newArray[r][0] +
", " +
newArray[r][2] +
", " +
newArray[r][3] +
", " +
newArray[r][4]
);
});
newArray.forEach((r, o) => {
ratingStartCell.setValues(newArray[r][2]);
});
}

By the way, I modified your array. I'm not sure why it contains "()", you can refer to JavaScript Array to learn more on how to declare and use arrays.
You can refer to this sample code:
function testArray() {
var activeSpreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var activeSheet = activeSpreadsheet.getActiveSheet();
Logger.log(activeSheet.getSheetName());
var newArray = [
["John Buck", "*****", "Associate", "Luxura"],
["Jane Doe", "****", "Manager", "Eclipse"],
["Jim Bob", "***", "Executive", "lokia"],
["Suzzy Sue", "*****", "Director", "RoomX"]
];
Logger.log(newArray);
//Create new array that will contain expected row values
var rowValues = [];
newArray.forEach((r,o)=>{
//Push a 2-D array, Col1 will be the rating (****) while Col2 will be the remaining strings
//separated by a comma and space
rowValues.push([r[1], r[0]+", "+r[2]+", "+r[3]]);
});
Logger.log(rowValues);
//Set the values for the selected range, using the size of the 2-d array starting from row 5.
activeSheet.getRange(5,3,rowValues.length,rowValues[0].length).setValues(rowValues);
}
What it does?
After fixing your array, I created a new array rowValues which will contain 2 information:
Rating
Concatenated strings separated by a comma and a space.
Once I have created the desired grouping and output for each row. I now set the values of the selected range based on the rowValues created using Sheet.getRange(row, column, numRows, numColumns) and Range.setValues(values)
I set the start row to 5 and start column to 3 based on your earlier example. Feel free to adjust your start row and column index based on your preference.
OUTPUT:

You could post the newArray to activeSheet in one go using the setValues(values) method.
This is more efficient than writing to the sheet on cell at a time. See Best Practices.
activeSheet.getRange(1, 1, newArray.length, newArray[0].length).setValues(newArray);
The size of the array does not need to be specified. newArray.length is the number of rows and newArray[0].length is the number of columns.
You need to be careful when constructing the array: each row must have the same number of columns. If the number of columns is different, you'll get an error and the setValues() will fail.

Related

Match Entire Cell for Multiple Find and Replace in Google Sheets via Google Apps Script

First question here - I'm trying to use the Multiple Find and Replace in Google App Scripts for Google Sheets from this thread, however, I need to do an exact match on the cell. I did some research and see mentions of the class TextFinder and method matchExactCell, but I am stumped on where to add it.
When the script is run multiple times, then the first name in the replace is appended multiple times so the replaced cell reads: "John John John Smith" if script is run 3 times.
Any recommendations are appreciated! Thanks!
function runReplaceInSheet(){
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1");
// get the current data range values as an array
// Fewer calls to access the sheet -> lower overhead
var values = sheet.getDataRange().getValues();
// Replace Names
replaceInSheet(values, 'Smith', 'John Smith');
replaceInSheet(values, 'Doe', 'Jane Doe');
// Write all updated values to the sheet, at once
sheet.getDataRange().setValues(values);
}
function replaceInSheet(values, to_replace, replace_with) {
//loop over the rows in the array
for(var row in values){
//use Array.map to execute a replace call on each of the cells in the row.
var replaced_values = values[row].map(function(original_value) {
return original_value.toString().replace(to_replace,replace_with);
});
//replace the original row values with the replaced values
values[row] = replaced_values;
}
}
You can try with this little modification that does not use the function "replace" but compares the whole value with "replace_to" and returns "replace_with" if it's equal or "original_values" if it's not:
function replaceInSheet(values, to_replace, replace_with) {
//loop over the rows in the array
for(var row in values){
//use Array.map to execute a replace call on each of the cells in the row.
var replaced_values = values[row].map(function(original_value) {
if(original_value == to_replace) {return replace_with}
else {return original_value};
});
//replace the original row values with the replaced values
values[row] = replaced_values;
}
}
As another approach, from I did some research and see mentions of the class TextFinder and method matchExactCell, but I am stumped on where to add it., if you want to use TextFinder, how about the following sample script?
Sample script:
function sample1() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1");
var obj = [
{ find: "Smith", replacement: "John Smith" },
{ find: "Doe", replacement: "Jane Doe" }
];
var range = sheet.getDataRange();
obj.forEach(({ find, replacement }) => range.createTextFinder(find).matchEntireCell(true).replaceAllWith(replacement));
}
Although the process cost of TextFinder is low, in this case, the replacement is run in a loop. If you want to reduce the process cost more, how about the following sample script? In this sample, Sheets API is used. By this, the process cost is lower a little than that of the above. Before you use this script, please enable Sheets API at Advanced Google services.
function sample2() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheetId = ss.getSheetByName("Sheet1").getSheetId();
var obj = [
{ find: "Smith", replacement: "John Smith" },
{ find: "Doe", replacement: "Jane Doe" }
];
var requests = obj.map(({ find, replacement }) => ({ findReplace: { find, replacement, range: { sheetId }, matchEntireCell: true } }));
Sheets.Spreadsheets.batchUpdate({ requests }, ss.getId());
}
References:
createTextFinder(findText) of Class Range
Method: spreadsheets.batchUpdate
FindReplaceRequest

How can I exclude null elements from data array?

I have been using Yuri's answer to a question, but found myself in a new situation and I haven't quite understood each step of this method here:
function myFunction() {
var sheet = SpreadsheetApp.getActiveSheet();
var header = sheet.getDataRange().getValues()[0];
var data = [
["ID","Variação","Nome Site","Obs"],
[11602,185,"Camisa Teste","Teste da Observação"]
]
var indexes = header.map(x => data[0].indexOf(x));
data = data.map(x => indexes.map(i => x.slice()[0] = x[i]));
sheet.getRange(1, 1, data.length, data[0].length).setValues(data);
}
Here's what the result looks like:
let result = [
["ID","Variação","Nome Site","Obs", "", ""],
[11602,185,"Camisa Teste","Teste da Observação", "", ""]
]
Now, this is returning data with nulls. How can I make it return only mapped array elements?
So, instead of heaving an array length of 6, that'd be only 4.
Expected Result:
let expectedResult = [
["ID","Variação","Nome Site","Obs"],
[11602,185,"Camisa Teste","Teste da Observação"]
]
Thank you!
One possible reason why you are having null elements in your array is that the cells next to your header have " " or space character in it. Since you are using getDataRange() it will also include those " " characters in your array.
Example:
Here, I added space character to cells E1 and F1:
Here are the values of data after using map function:
If I remove the values of cells E1 and F1 the output of data is:
Null values can also occur if the values below the header overlap the number of columns in your header.
Example:
Output:
Solution:
Instead of using getDataRange(), explicitly define the range you are using for the header. You can use either getRange(row, column, numRows, numColumns) or getRange(a1Notation).
Reference:
getRange(a1Notation)
getRange(row, column, numRows, numColumns)
One way is to filter the inner values:
let result = [
["ID", "Variação", "Nome Site", "Obs", "", ""],
[11602, 185, "Camisa Teste", "Teste da Observação", "", ""]
]
result = result.map(x => x.filter(y => y))
console.log(result)
Remove nulls
function test() {
Logger.log(JSON.stringify([1,2,3,null,4,null,5,''].filter(e => e)))
}
Execution log
7:48:37 PM Notice Execution started
7:48:37 PM Info [1,2,3,4,5]
7:48:38 PM Notice Execution completed

How to Extract data based on the values in one array after matching the corresponding values from another array in JavaScript?

This is the URL from GeoServer to get feature info
{"type":"FeatureCollection","features":[{"type":"Feature","id":"weather_warning_day_1.fid--418ec0da_178b69d5dfc_-715c","geometry":null,"properties":{"issue_date":"2021-04-09","updated_at":"2021-04-09T09:26:33+05:30","utc_time":0,"state_name":"Odisha","state_id":21,"district_name":"MAYURBHANJ","district_id":232,"api_district_name":"MAYURBHANJ","day_1":"6,9,10","day1_color":3}}],"totalFeatures":"unknown","numberReturned":1,"timeStamp":"2021-04-09T15:38:19.536Z","crs":null}
the data I want to extract is of variable: "day_1":"6,9,10"
which I got from the layer and stored it in the variable as
var warning_day_1 = weather_warning_layer_data.features[0].properties.day_1
so basically the input is "day_1":"6,9,10"
which I have stored in the array as
[{"warning":"6"},{"warning":"9"},{"warning":"10"}]
and corresponding output should be Dust Storm, Heat Wave, Hot Day
Dust Storm, Heat Wave, Hot Day
or if the input was "day_1":"2,5"
then output should have been Heavy Rain, Hailstorm
or if the input was "day_1":"1"
then output should have been No Warning
After reading the data of the string and creating its array, I have to compare it with another array and extract the key values (display) corresponding to the key values (warning) in the 1st array.
var warning_data_split = warning_day_1.split(/[ ,]+/);
var warning_data_from_api_array = new Array;
warning_data_from_api_array.push(warning_data_split);
for (var i = 0; i < warning_data_from_api_array.length; i++) {
var item_in_array_to_compare = warning_data_from_api_array[i];
if(warning_data_from_api_array[item_in_array_to_compare.warning_data_from_api_array])
{warning_data_from_api_array[item_in_array_to_compare.warning_data_from_api_array].push(item_in_array_to_compare);}
else {
warning_data_from_api_array[item_in_array_to_compare.warning_data_from_api_array] = [item_in_array_to_compare];}}
let final_array_to_compare = item_in_array_to_compare
final_array_to_compare = final_array_to_compare.map(x => ({warning: x}));
/// this is the first array ////////////
The values in this array are not static in length, as it keeps on changing like, sometimes the array has value [1] or [1,2], [2,5,8], [4,7,12], etc
so I have to extract the corresponding values of display from the lookup array given below
var warning_code_meaning_list = [
{ warning:"1", display:"No Warning"},
{ warning:"2", display:"Heavy Rain"},
{ warning:"3", display:"Heavy Snow"},
{ warning:"4", display:"Thunderstorm & Lightning, Squall etc"},
{ warning:"5", display:"Hailstorm"},
{ warning:"6", display:"Dust Storm"},
{ warning:"7", display:"Dust Raising Winds"},
{ warning:"8", display:"Strong Surface Winds"},
{ warning:"9", display:"Heat Wave"},
{ warning:"10", display:"Hot Day"},
{ warning:"11", display:"Warm Night"},
{ warning:"12", display:"Cold Wave"},
{ warning:"13", display:"Cold Day"},
{ warning:"14", display:"Ground Frost"},
{ warning:"15", display:"Fog"}
]
The data which I am getting in warning_day_1 (in the very first line of the code) is a string (this couldn’t be saved as float/integer in the database column because sometimes there are more than 1 warning for a specific place, so I have stored this as a text in the database)
Which I’m converting to an array after reading it from the API
Now this string, which I am fetching from API has variable data,
Some time single digit like: 1
Sometime multiple : 1,2,3
And each of the integer present in this array corresponds to the specific text shown in the next array like if the warning is 2 it means the heavy rainfall,
but if the string (later converted to an array, with “warning” as a key) has 2,5 as value, it means: heavy rainfall & Hailstorm
I want that the values which come up in array 1 (the dynamic one) got match with the 2nd array ( a sort of lookup array) and fetch its display value as output.
How to do so?
You could use an object to map your warnings to messages.
Try this:
const data = {"type":"FeatureCollection","features":[{"type":"Feature","id":"weather_warning_day_1.fid--418ec0da_178b69d5dfc_-715c","geometry":null,"properties":{"issue_date":"2021-04-09","updated_at":"2021-04-09T09:26:33+05:30","utc_time":0,"state_name":"Odisha","state_id":21,"district_name":"MAYURBHANJ","district_id":232,"api_district_name":"MAYURBHANJ","day_1":"6,9,10","day1_color":3}}],"totalFeatures":"unknown","numberReturned":1,"timeStamp":"2021-04-09T15:38:19.536Z","crs":null}
var warning_code_meaning_list = {
"1":"No Warning",
"2":"Heavy Rain",
"3":"Heavy Snow",
"4":"Thunderstorm & Lightning, Squall etc",
"5":"Hailstorm",
"6":"Dust Storm",
"7":"Dust Raising Winds",
"8":"Strong Surface Winds",
"9":"Heat Wave",
"10":"Hot Day",
"11":"Warm Night",
"12":"Cold Wave",
"13":"Cold Day",
"14":"Ground Frost",
"15":"Fog",
};
results = data["features"].map(feature => {
return feature.properties.day_1.split(',').map(code => {
return warning_code_meaning_list[code];
});
});
That gives you an array of arrays of the displays:
[ [ 'Dust Storm', 'Heat Wave', 'Hot Day' ] ]

Move Specific Rows depending on Filtering Keywords within unknown amount of rows using Google Sheets Apps Scripts

I do SEO, and therefore I have a lot of keywords flowing around in different spreadsheets. I'd like a way to filter these into seperate sheets based on specific filters, but I can't for the life of me, figure out how to do this in Google Apps Script.
Criteria I set myself for this to work out:
A list of strings and their corresponding volumes are entered in column 1+2.
A list of filter-words are written in column 3.
The script has to create a new sheet for each of the filter words and move the strings + volumes into these different sheets if the string contains a filter word.
Example:
Filter words: Apple, Banana, Pineapple
String: "The Apple Was Big", Volume: "100"
The script would move the string and volume into the sheet called "Apple" on row 1
(Beware, I'm in no means experienced in coding)
I believe you can use the following structure:
for(let i = 0; i <= column3RowAmount; i++){ //Run as long as there are more filter words
create(column3Row[i]); //create a new sheet with the name of the filter word
for(let j = 0; j <= column1RowAmount; j++){ //Run as long as there are more keywords
if(column1Row[j].indexOf(column3Row[i]) >= 0){ //If the Row in column 1 contains the filter word
column1Row[j].moveToSheet(column3Row[i]); // Make sure not to move Column 3, but only 1+2
}
}
}
Example sheet: https://docs.google.com/spreadsheets/d/15YIMyGmmfZdy094gwuJNxFmTd8h7NOLnA8KevZrGtdU/edit?usp=sharing
Explanation:
Your goal is to create a sheet for every filter-word in column C. Then copy the data in columns A, B but only the rows that include the filter-word to the corresponding sheet.
For starters, you need to get the filter-word list. You can get the full range of column C and filter out the empty cells:
const sh_names = sh.getRange('C1:C').getValues().flat().filter(r=>r!='');
Similarly, you need to get the data in columns A and B:
const data = sh.getRange('A1:B'+sh.getLastRow()).getValues();
The next step is to iterate over sh_names and for every element / filter-word, check if a sheet with that name exists. If it does not exist, then create a sheet with that name, if it exists then skip the creation part:
if(!ss.getSheetByName(s)){
ss.insertSheet().setName(s);}
The next step is to filter data on the rows that include the filter-word:
let f_data = data.filter(r=>r[0].includes(s));
Finally, check if the length of the data is bigger than 0, otherwise there is not data to use and set the values of data to the corresponding sheet:
sheet.getRange(sheet.getLastRow()+1,1,f_data.length,f_data[0].length).setValues(f_data)
Solution
function myFunction() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName('Ark1');
const filter_sh = ss.getSheetByName('Filter');
const data = sh.getRange('A1:B'+sh.getLastRow()).getValues();
const sh_names = filter_sh.getRange('A1:A'+filter_sh.getLastRow()).getValues().flat();
sh_names.forEach(s=>{
if(!ss.getSheetByName(s)){
ss.insertSheet().setName(s);}
let sheet = ss.getSheetByName(s);
let f_data = data.filter(r=>r[0].includes(s));
if(f_data.length>0){
sheet.getRange(sheet.getLastRow()+1,1,f_data.length,f_data[0].length).setValues(f_data);}
});
}
This function will place all of your results into column 4 next to the appropriate word rather than creating a page for each word. So it runs much faster.
function stringswords() {
const ss=SpreadsheetApp.getActive();
const sh=ss.getSheetByName('Sheet1');
const sr=2;
const rgd=sh.getRange(sr,1,sh.getLastRow()-sr+1,2);
const data=rgd.getDisplayValues();
const rgw=sh.getRange(sr,3,sh.getLastRow()-sr+1,1);
const words=rgw.getDisplayValues().flat();
const wiObj={};
words.forEach(function(w,i){wiObj[w]=i});
const rgr=sh.getRange(sr,4,sh.getLastRow()-sr+1,1);
rgr.clearContent();
var results=rgr.getValues();
words.forEach(function(w,i,A){
data.forEach(function(r,j,D) {
if(data[j][0] && data[j][0].indexOf(w)!=-1) {
results[wiObj[w]][0]+=Utilities.formatString('String:%s Vol:%s\n',data[j][0],data[j][1]);
}
});
});
rgr.setValues(results);
}
Image of Data and output:

How do I Filter array from two columns to non-null pairs?

With the code below, I have a Google Sheet, and I'm trying to create arrays from different sections.
function onFormSubmitTest(e) {
//Open spreadsheet based on URL
var ss = SpreadsheetApp.openByUrl('sheetnamehere');
//Set as Active
SpreadsheetApp.setActiveSpreadsheet(ss);
//Set Tabs as Variables
var Rsheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Emailer");
var Lsheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Unique Databases");
//----Set last Rows as Variables----
// Gets Values from different sections
var formResponses = Rsheet.getRange("N3:N").getValues();
var databaseNames = Lsheet.getRange("A2:B").getValues();
var developerNames = Lsheet.getRange("F2:F").getValues();
// Filters to ranges that have values, ignores null
var lastRvalues = formResponses.filter(String);
var lastLvalues = databaseNames.filter(String);
var lastDvalues = developerNames.filter(String);
// Sorts arrays for faster indexing
var lastR = lastRvalues.sort();
var lastL = lastLvalues.sort();
var lastD = lastDvalues.sort();
// Unique Databases
var currentDatabases = lastL;
// Current Developers
var currentDevelopers = lastD
On the Unique Databases tab, I have 100 rows on the sheet, with only 28 of them having data. If I have the code below, I can see it only has 28 values as expected:
var databaseNames = Lsheet.getRange("A2:A").getValues();
However, when I add the other column, I get 100 values, with the vast majority being ["", ""]. Should note that the 28 values are key>value pair, so if there's a value in A:A, there will be one in B:B. If null in A:A, then B:B is null.
What am I doing wrong?
Indeed, [["", ""], ["", ""]].filter(String) returns the entire array. To see why, replace filter by map:
[["", ""], ["", ""]].map(String) // [",", ","]
Those are truthy strings, with a comma.
A correct way to filter text by nonemptiness of the first column of the range would be
range.getValues().filter(function(row) {return row[0]})
(The usual caveat about 0 value being falsy applies, but it doesn't look like you would encounter numeric 0 there, the column being text. And the string "0" is truthy.)
(Also, "null" is not the right term here, Apps Script returns empty strings "" for empty cells, which is different from null)

Categories

Resources