GAS - Get Certain columns in 2D array based on list - google-apps-script

I am trying to build a custom array with Google sheet data by getting certain columns in a certain order. The columns/order is set on a google sheet. The below works but I would like to make the return on row 11 more dynamic.
function buildArray() {
const s = SpreadsheetApp;
const ss = s.getActiveSpreadsheet();
const sheet = ss.getSheetByName('Test Sheet');
const pasteSheet = ss.getSheetByName('Paste');
const data = sheet.getRange(1,1,10,10).getValues(); //update range
// const headers = pasteSheet.getRange(1,1,pasteSheet.getLastRow(),1).getValues().map(function(r){return r}).flat(Infinity); //list of headers needed in 1d array
const colNeed = [0,9,5,6,4,7]; //array index for column numbers
var customArray = data.map(function(r){
return [r[0], r[9], r[5], r[6], r[4], r[7]]; //Would like to make this more dynamic
})
debugger;
pasteSheet.getRange(1, 2, pasteSheet.getLastRow(), pasteSheet.getLastColumn()).clear();
pasteSheet.getRange(1, 2, customArray.length, customArray[0].length).setValues(customArray);
debugger;
}
I have tried replacing the r[0] with r[colNeed.map(...)] and also a for loop with no success.
Any ideas on how to make this work.
Here is a screenshot of the final sheet.
I just copied/pasted the header names in the first column(A) to make sure the right columns were pulled/pasted.

Try this one using a loop to combine the data first then return. Also added the headers on top of customArray.
Code:
function buildArray() {
const s = SpreadsheetApp;
const ss = s.getActiveSpreadsheet();
const sheet = ss.getSheetByName('Test Sheet');
const pasteSheet = ss.getSheetByName('Paste');
const data = sheet.getRange(1,1,10,10).getValues(); // update range
// get headers as single array (excluding blank cells)
// since last column of data can go beyond last row of column A
// headers can include blank cells if there is already data present
const headers = pasteSheet.getRange(1,1,pasteSheet.getLastRow(),1).getValues().flat().filter(Boolean);
// array index for column numbers
const colNeed = [0,9,5,6,4,7];
var customArray = data.map(function(r){
var output = [];
colNeed.forEach(function (col){
output.push(r[col]);
});
return output;
});
// Add headers at the first element of customArray
customArray.unshift(headers)
debugger;
pasteSheet.getRange(1, 2, pasteSheet.getLastRow(), pasteSheet.getLastColumn()).clear();
pasteSheet.getRange(1, 2, customArray.length, customArray[0].length).setValues(customArray);
debugger;
}
Paste:
Test Sheet:
Paste Sheet after executing buildArray:

Related

Google Apps Script - Better way to do a Vlookup

I am doing a kind of VLOOKUP operation in a column with about 3K cells. I am using the following function to do it. I commented on what the code is doing in the function, but to summarize:
It creates a map from values to search for from a table with metadata
It iterates each value of a given range, and searches for coincidences in the previous map
If coincidences are found, it uses the index to capture the second column of the metadata table
Finally, sets the value captured in another cell
This is the code:
function questions_categories() {
var ss = SpreadsheetApp.getActive();
var sheet = ss.getSheetByName("data_processed");
// get metadata. This will work as the table to look into
// Column B contains the matching element
// Column C contains the string to return
var metadata = ss.getSheetByName("metadata").getRange('B2:C').getValues()
// Just get the different values from the column B
var dataList = metadata.map(x => x[0])
// Used to define the last cell where to apply the vlookup
var Avals = sheet.getRange("A1:A").getValues();
var Alast = Avals.filter(String).length;
// define the range to apply the "vlookup"
const questions_range = sheet.getRange("Q2:Q" + Alast);
forEachRangeCell(questions_range, (cell) => {
var searchValue = cell.getValue();
// is the value to search in the dataList we defined previously?
var index = dataList.indexOf(searchValue);
if (index === -1) {
// if not, throw an error
throw new Error('Value not found')
} else {
// if the value is there, use the index in which that appears to get the value of column C
var foundValue = metadata[index][1]
// set the value in two columns to the right
cell.offset(0, 2).setValue(`${foundValue}`);
}
})
}
forEachRangeCell() is a helper function to iterate through the range.
This works very well, but it resolves 3-4 cells per second, which is not very efficient if I need to check thousands of data. I was wondering if there is a more performant way to achieve the same result.
To improve performance, use Range.setValues() instead of Range.setValue(), like this:
function questions_categories() {
const ss = SpreadsheetApp.getActive();
const source = { values: ss.getRange('metadata!B2:C').getValues() };
const target = { range: ss.getRange('data_processed!Q2:Q') };
source.keys = source.values.map(row => row[0]);
target.keys = target.range.getValues().flat();
const result = target.keys.map(key => [source.values[source.keys.indexOf(key)]?.[1]]);
target.range.offset(0, 2).setValues(result);
}
See Apps Script best practices.

Update Google Form Choices Info

We want to make a Google Form where there are dropdown options pulled from column F of the sheet (Sheet1), beginning in row 3 on down. However, column F has formulas in it, specifically: =IF(D$="","", CONCATENATE(C$," - ",D$)), so that some of the cells appear blank while others have visible text.
The code we attempted to use below does not work. Any help on how to make this work by pulling choices from column F, but of course ignoring blank cells?
var form = FormApp.openById('1Hg4TvEZUnzIMZI_andbwHQ3jtaIBLOZsrTkgjSwVcAY')
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1');
const current = sheet.getRange(3,6,sheet.getLastRow()-1,6).getValues()
var range = sheet.getDataRange();
var rangeList = current.map(function (row, i) {
for (var i=rangeList; i<range.length; i++) {
if (row[5] == "") return;
var matched = row[5];
const Question = form.getItemById ("620176576")
Question.asListItem().setChoiceValues(matched)
}
})
}
You've to use filter to only get the values which are not null.
Try below sample script:-
const form = FormApp.openById('1Hg4TvEZUnzIMZI_andbwHQ3jtaIBLOZsrTkgjSwVcAY')
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1')
const current = sheet.getRange(3,6,sheet.getLastRow()-3).getValues().flat().filter(r=> r) //filtering out blank values
const Question = form.getItemById("620176576")
Question.asListItem().setChoiceValues(current)
Reference:
filter()

Find text and copy row to other sheet

Hi I have a list of data in Google Sheets that I want to use to extract certain names based upon another list.
Following code works to point me out on what row the search_string is:
function searchString(){
var sheet = SpreadsheetApp.getActiveSheet()
var search_string = "searchname1"
var textFinder = sheet.createTextFinder(search_string)
var search_row = textFinder.findNext().getRow()
var ui = SpreadsheetApp.getUi();
ui.alert("search row: " + search_row)
}
I want to find out how I can add multiple search_strings like below. Its quite a list.
I tried array brackets but that gives me an error of NULL.
function searchString(){
var sheet = SpreadsheetApp.getActiveSheet()
var search_string = ("searchname1","searchname2")
var textFinder = sheet.createTextFinder(search_string)
var search_row = textFinder.findNext().getRow()
var ui = SpreadsheetApp.getUi();
ui.alert("search row: " + search_row)
}
Then instead of showing me an alert I want to copy the rows that are found to a new sheet.
In the subsequential order of the array of search_strings.
Explanation:
Your goal is to copy the rows where the strings appear (for the first time) in the source sheet to a target sheet.
You can include all the strings you want to search in the search_strings array.
Iterate over each string to find the first row it appears and add that row to the rows array.
Then you can get all the data from the source sheet and filter only the rows that are part of the rows list:
var data = source_sheet.getDataRange()
.getValues().filter((_,i)=>rows.includes(i+1));
Finally copy the data to the target sheet.
Solution:
function searchString(){
const ss = SpreadsheetApp.getActive();
const source_sheet = ss.getSheetByName('Sheet1'); // change the name
const target_sheet = ss.getSheetByName('Sheet2'); // change the name
const search_strings = ["searchname1","searchname2"]; // add your strings to search
const rows = [];
search_strings.forEach(search_string=>{
let textFinder = source_sheet.createTextFinder(search_string);
try{
let search_row = textFinder.findNext().getRow();
rows.push(search_row);
} catch(e){}
});
const data = source_sheet.getDataRange()
.getValues().filter((_,i)=>rows.includes(i+1));
if(data.length>0){
target_sheet.getRange(1,1,data.length,data[0].length).setValues(data);
}
}

Google Sheets appendRow and add values to specific columns

I have developed a sidebar form that appends values to an existing spreadsheet.
The form works; however, the new values are added starting at column A. I would like to adapt my code to select the columns to which appended values are inserted. The backend code used to append data is:
function addNewRow(rowData) {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const ws = ss.getSheetByName("Recruitment_Contacts");
ws.appendRow([rowData.LnRT,rowData.FnRT,rowData.Gn,rowData.St,rowData.Dtr,rowData.Trn,rowData.Td]);
return true;
}
The code used within HTML script to add data after the submit button is selected is:
function afterButtonClicked(){
var ln = document.getElementById("LnRT");
var fn = document.getElementById("FnRT");
var gn = document.getElementById("Gn");
var st = document.getElementById("St");
var dtr = document.getElementById("Dtr");
var trn = document.getElementById("Trn");
var td = document.getElementById("Td");
var rowData = {LnRT: ln.value, FnRT: fn.value, Gn: gn.value, St: st.value, Dtr: dtr.value, Trn: trn.value, Td: td.value};
google.script.run.withSuccessHandler(afterSubmit).addNewRow(rowData);
}
Any support that the community can provide would be greatly appreciated.
Thank you.
I think this is what you're looking for:
Selecting content by id
function addNewRow(rowData) {
const ss=SpreadsheetApp.getActive();
const sh=ss.getSheetByName("Recruitment_Contacts");
const hA=sh.getRange(1,1,1,sh.getLastColumn()).getValues()[0];
idx={};
hA.forEach(function(h,i){idx[i]=h;});
ws.appendRow([rowData[idx[0]],rowData[idx[1]],rowData[idx[2]],rowData[idx[3]],rowData[idx[4]],rowData[idx[5]],rowData[idx[6]]]);
return true;
}
I do this a lot and I just hide the top row and it doesn't seem to bother anybody and warn them that if the screw it up I get to come and charge more for fixing it.
Solution
To achieve what you are aming for of appending a new row from the column index you want, you must use the Apps Script method setValues on the right range. Here is the code implementation with self explanatory comments to achieve this:
// you can pass a column index to this function as a new parameter (or inside rowData)
function addNewRow(rowData,column) {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const ws = ss.getSheetByName("Recruitment_Contacts");
// Set as the data what we want to insert to then be able to know how many
// columns will this fill
// We need to wrap the data into [] as it is expecting a nested array for [cols] and [rows]
var data = [[rowData.LnRT,rowData.FnRT,rowData.Gn,rowData.St,rowData.Dtr,rowData.Trn,rowData.Td]];
// Set the values on the first row of the sheet (reproducing the behaviour of appendRow)
// and starting from the row index you want (5 for example).
// It will be 1 row and the length of the data
// long in terms of columns.
ws.insertRows(1,1);
ws.getRange(1, column,1,data[0].length).setValues(data);
return true;
}
I hope this has helped you. Let me know if you need anything else or if you did not understood something. :)
To resolve my issue, I created a server side function called "recruitInfo" and listed all the columns in my worksheet. I then called that function in my HTML code. The video I used to walk me through this process is Web App - Google Sheets CRUD, Part 5
function addRecruit(recruitInfo){
const ss = SpreadsheetApp.getActiveSpreadsheet();
const ws = ss.getSheetByName("sheetName");
const uniqueIds = ws.getRange(2, 1, ws.getLastRow()-1, 1).getValues();
var maxNum = 0;
uniqueIds.forEach(r => {
maxNum = r[0] > maxNum ? r[0] : maxNum
});
var newID = maxNum + 1;
ws.appendRow([newID,
recruitInfo.lastName,
recruitInfo.firstName])
}
function addRecruit(){
var recruitInfo = {};
recruitInfo.firstName = document.getElementById("firstName").value;
recruitInfo.lastName = document.getElementById("lastName").value;
google.script.run.withSuccessHandler(function(){}).addRecruit(recruitInfo);
}

Google Sheets Macro for deleting columns in specific tabs based on header

I wrote a macro that creates sheet tabs and populates them based on specific criteria. For example, if I want to isolate rows indicating Closed Won, and move them to a new tab. I will run this function on my main tab called 'data' and create a new tab called 'Closed Won'.
This new tab will duplicate the same header as in 'data', and then it will populate with all rows with "Closed Won" in column L.
However, this new tab has more data than I need. I want to delete specific columns IF they have a column name AND tab name (so it does not delete the columns in my original data tab).
I am having trouble with the IF. Can someone help with a simple script that I can add to the end of the original function?
function closed_won() {
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.getRange('1:1').activate();
spreadsheet.insertSheet(1);
spreadsheet.getRange('1:1').activate();
spreadsheet.getActiveSheet().setName('closed_won');
spreadsheet.getRange('data!1:1').copyTo(spreadsheet.getActiveRange(), SpreadsheetApp.CopyPasteType.PASTE_VALUES, false);
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('data');
var testrange = sheet.getRange('L:L');
var testvalue = (testrange.getValues());
var csh = ss.getSheetByName('closed_won');
var data = [];
var j =[];
for (i=0; i<testvalue.length;i++) {
if ( testvalue[i] == 'Closed Won') {
data.push.apply(data,sheet.getRange(i+1,1,1,25).getValues());
j.push(i);
}
}
csh.getRange(csh.getLastRow()+1,1,data.length,data[0].length).setValues(data);
// THIS IS WHERE I WANT TO ADD THE DELETE COLUMN CODE
}
You want to delete columns where the header name matches a sheet name - though obviously not on the current sheet.
The process is:
get the sheets and their names; save them in an array
get the header row. This will be a 2D array, so "flatten" it to 1D for easier matching
loop through the headers and look for a match with sheet names. The code uses indexOf for this.
3.1 when you find a match of header name and column name, make sure that its not the match for the current sheet
3.2. create an array of the column numbers to be deleted.
After the loop, reverse the order of the array so that the "highest" to-be-deleted column numbers are listed first.
Loop through the to-be-deleted column numbers, and delete the columns
function SO5819343001() {
var spreadsheet = SpreadsheetApp.getActive();
// get sheets names
var thesheets = [];
var sheets = spreadsheet.getSheets();
if (sheets.length > 1) {
for (var i=0; i<sheets.length;i++){
thesheets.push(sheets[i].getName());
}
}
// move to 'closed_won'
var csh = spreadsheet.getSheetByName('closed_won');
// get the headers
//last column for range
var cshLC = csh.getLastColumn();
var headers = csh.getRange(1,1,1,cshLC).getValues();
// flatten headers from 2D to 1D
var flatheaders = headers.reduce(function(a, b){return a.concat(b);});
// Logger.log(flatheaders); DEBUG
// create variables for loop
var cshname = csh.getName();
var deletions = [];
// loop through the headers and compare to sheet names
for (var h = 0; h<cshLC;h++){
var idx = thesheets.indexOf(flatheaders[h]);
// Logger.log("DEBUG: h = "+h+", header = "+flatheaders[h]+", match = "+idx)
// is this a match?
if (idx !=-1){
// found a match for column name and sheet name
// make sure it is not this sheet
if (flatheaders[h] != cshname){
// Logger.log("DEBUG: the match is NOT on this sheet. Proceeding")
// create an array of the column numbers to be deleted
deletions.push(h+1);
}
else{
Logger.log("the match IS on this sheet. Abort.")
}
}
}
// Logger.log(deletions); DEBUG
// reverse the column order
var rev_deletions = deletions.reverse();
// Logger.log(rev_deletions); // DEBUG
// loop through the 'to-be-deleted' columns and delete them
for (d = 0;d<deletions.length;d++){
csh.deleteColumn(rev_deletions[d]);
}
}