Concatenate a dynamic Number of columns Google Script - javascript

I need to concatenate x number of columns per project sometimes it's 3 columns others 7 or 5, it just depends
I am trying to this with an array of range column numbers ex [2,5,3]
columns 2,5 3 in that order with a delimiter here |
I have searched but found only static concatenating functions
I have a VBA Macro that works as I need in Excel so I am trying to write it in Google Script
The function runs without erroring but nothing is posted back
From Logger.log() I am kinda close to the proper structure
I get undefined|b|e|c
I want to post back to the last column + 1
I am not sure this is the best way to do this but it what I have
Any help is appreciated, Thanks
colA ColB ColC ColD ColE ColF ColG ColH
a b cc d e f g b|e|c
a2 b2 d2 e2 f2 g2 e2|c2
ect.
Here is what I have:
function TemplateA_n() {
Template_A("A", [2, 4, 6])
}
function Template_A(SshtName, sArr){
var sSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SshtName);
var sR = sSheet.getDataRange();
var sV = sR.getValues();
var sLC = sSheet.getLastColumn();
var sLR = sSheet.getLastRow();
var a = []
//Rows
for (var row = 2; row < sLR; row++){
a[row] =[]
//Columns
for (var col = 0; col < sArr.length ; col++){
if(sV[row][sArr[col]] !== "") {
if(sV[row][sArr[0]] == "") {
a[row][0] = a[row][0] + sV[row][sArr[col]];
Logger.log(a[row][0])
}
else {
a[row][0] = a[row][0] + "|" + sV[row][sArr[col]];
Logger.log(a[row][0])
}
}
}
}
sSheet.getRange(1,sLC + 1,sLR,1);
}
Here is the Macro
Sub ConCatA()
Dim rng As Range, r As Range, i As Long
On Error Resume Next
Set rng = Application.InputBox("Select column(s)", Type:=8)
'Set rng = Range("B1,A1,C1")
On Error GoTo 0
If rng Is Nothing Then Exit Sub
With ActiveSheet.UsedRange
ReDim a(1 To .Rows.Count, 1 To 1)
a(1, 1) = "Concat"
For i = 2 To .Rows.Count
For Each r In rng
If .Cells(i, r.Column) <> "" Then
a(i, 1) = a(i, 1) & IIf(a(i, 1) = "", "", "|") & .Cells(i, r.Column).value
End If
Next r
Next i
With .Offset(, .Columns.Count).Resize(, 1)
.value = a
End With
End With
End Sub

Part 1: Undefined value in output
The reason you get the following ouput: undefined|b|e|c is because the variable a[row][0] is undefined before you assign it any value. So when program runs the following line of code for the first time in the loop it concats the value of sV[row][sArr[col]]to undefined.
a[row][0] = a[row][0] + sV[row][sArr[col]]
All you need to do is assign an empty value to begin with, like so
for (var row = 2; row < sLR; row++){
a[row] =[]
a[row][0] = ""
... your code here
}
Also, since the assignment of values only start from index 2 in the loop, we need to assign index 0 and 1.
a[0] = [""]
a[1] = [""]
This will enable us to input blank values in the sheet when we use setvalues function with this array.
Part 2: Append values to sheet (lastColumn + 1)You define the range to append your data and then set its values, as follows:
var appRange = Sheet.getRange(2,sLC+1,a.length,1)
appRange.setValues(a)
Your final code would look like this:
function Template_A(SshtName, sArr){
var sSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SshtName);
var sR = sSheet.getDataRange();
var sV = sR.getValues();
var sLC = sSheet.getLastColumn();
var sLR = sSheet.getLastRow();
var a = []
//Rows
a[0]= [""] // Since you are start from index 1, you need to assign a value to index 0
for (var row = 1; row < sLR; row++){ //If you intended to start from 2nd row
// var row = 1 and not 2
a[row] = []
a[row][0] = "" //Empty value for each row
//Columns
for (var col = 0; col < sArr.length ; col++){
if(sV[row][sArr[col]-1] !== "") {
if(a[row][0] == "") { //Check to see if the array is empty
// If yes donot add "|"
a[row][0] = a[row][0] + sV[row][sArr[col]-1];
Logger.log(a[row][0])
}
else {
a[row][0] = a[row][0] + "|" + sV[row][sArr[col]-1];
Logger.log(a[row][0])
}
}
}
}
Logger.log(a)
var appRange = sSheet.getRange(1,sLC+1,a.length,1)
appRange.setValues(a)
}
Final Note: If you intend to skip the first row in your sheet your loop should start with counter 1. Since array index starts from 0 but row numbering in sheet start from 1.

Related

Building up a variable with numbers in brackets for a loop

I am beginning work on creating my first for loop (for 29 times - 0 to 28) but the numbers have to be presented in square brackets.
I eventually want to build of a string of 0's and 1's (zeros and ones). The value of cellData will always be 0 (zero) or 1 (one).
I want to change the cellindex variable first number starting at zero and finishing at 28. e.g.
And later do the same thing with the second number (in another loop).
[0,0]; [1,0]; [2,0]; [3,0];....... [28,0]; (First column).
[0,1]; [1,1]; [2,1]; [3,1];....... [28,1]; (Second column).
Where the first numbers represent the rows and the second numbers represents the columns in at table.
The idea being the row variable can be incremented each time in the the loop by 1.
First I need to find a way of setting up variables and then combining them together to get the variable: cellindex.
Unless there is a better way to do this?
Whatever I try, I keep getting not defined errors or cannot read property 'undefined' of undefined, Unexpected token etc...
I have included some of my poor attempts at building this below (commented in the code).
//var row = 0;
//var column = 0;
//var cellIndex = [row,column];
//var cellIndex = [row + "," + column];
//var cellindex = "[ + row + "," + column + ]";
//var cellindex = "[ + row + ',' + column + ]";
//var cellIndex = [+row+","+column+];
//var cellIndex = [ +row+ +','+ +column+ ];
var cellIndex = [0,0];
var cellData = sheet.getCellState(cellIndex);
var celldata = "";
celldata += (cellData);
console.log('Cell Data: ' + celldata);
Well the below works but it is huge code and has to be repeated for all 7 columns.
I cannot figure it out and if no-one else knows how to how to build up the the cellindex value with code then I cannot even attempt a loop to shorten it.
var cellIndex = [0,0];
var cellData = sheet.getCellState(cellIndex);
var celldata = "";
celldata += (cellData);
cellIndex = [1,0];
cellData = + sheet.getCellState(cellIndex);
var celldata = celldata;
celldata += (cellData);
cellIndex = [2,0];
cellData = + sheet.getCellState(cellIndex);
var celldata = celldata;
celldata += (cellData);
cellIndex = [3,0];
cellData = + sheet.getCellState(cellIndex);
var celldata = celldata;
celldata += (cellData);
//Repeat another 25 times
console.log('Cell Data: ' + celldata);
This is answered now, thanks to lucas.
I can output the data for each column in the format I require. Which is, for example:
1,1,1,1,1,0,1,0,1,0,1,0,1,1,1,0,1,0,0,0,1,0,0,0,0,1,1,1,1
If your cell index goes from [0,0] to [27,6] maybe this will help
var data ={};
for (var i=0;i<28;i++) {
for (var j=0;j<7;j++) {
var cellIndex = [i,j];
if(!data["col"+j]){
data["col"+j]=[]
}
var cellIndex = [i,j];
var cellData = sheet.getCellState(cellIndex);
data["col"+j].push(cellData)
}
}
console.log(data)
console.log(data.col1) // data.col1 will give you an array of column 1 values for further processing
console.log(data.col1.toString()) // this will give you a string you can copy from the console to paste elsewhere
If you wanted to split the data out into rows or columns you would just output an array (or something) at the end of each loop iteration
What about this
for (let i=0;i<28;++i) {
for (let j=0;j<28;++j) {
sheet.setCellState([i,j],[i,j]);
}
}
Checkout arrays in javascript since is the type of data you are presenting.
https://developer.mozilla.org/es/docs/Web/JavaScript/Referencia/Objetos_globales/Array

Iterating over cells in Google Spreadsheet Custom Function

I am trying to build a custom function in Google Spreadsheet, that would basically do this:
- say custom function called func() is placed in cell D2 and invoked as =func(B2)
- provided a particular cell reference (say B2) as a starting point it would iterate over all fields that follow B2 down the column (so B3, B4, B5) while the value of those fields equals to a particular symbol (say pipe |).
- For each iteration where this condition succeeds (i.e B3 == '|') it could add up/aggregate values from the cell it was placed, down the column. So if cells B3, B4,B5 contain | and then B6 doesn't it would return value of D3+D4+D5.
So for example if in this spreadsheet:
In the cells B10 the function should produce value of 8 (1+3+4) and in the cell B15 the function should produce value of 11 (5+6).
I've came up with something like this:
function sumByPipe(startRange) {
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getRange(startRange)
var sum = 0;
for (var row_num = 1; row_num < 128; row_num ++) {
var cell = range.getCell(row_num, 1);
var cellValue = cell.getValue();
if (cellValue == '|') {
sum += 1;
}
}
return sum;
}
and got stuck in 2 places really:
Function seems to work in the debugger, but when I invoke it from the spreadsheet it fails on the getRange() function call saying no such range exist. If I replace it with static call of say getRange('A2') that part works but then it fails on the getCell() saying index out of range.
How do I actually get the value of the next cell down the column from where the function itself is placed?
Really quite lost on these two and would appreciate any advice. Thank you!
This works. I tested it:
function sumByPipe(startRange) {
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getRange(startRange)
var sum = 0;
var startColumn = range.getColumn();
var startRow = range.getRow();
for (var row_num = startRow; row_num < startRow+128; row_num++) {
var cellWithPipe = sheet.getRange(row_num, startColumn-1).getValue();
var cellValue = sheet.getRange(row_num, startColumn).getValue();
if (cellWithPipe === '|') {
sum += cellValue;
} else {
//If pipe is no longer present, stop and return sum
return sum;
}
}
}

How can I merge Same Range from each file in a folder onto a master sheet?

I modified the code this way... it gathers data from all the sheets and finds only the rows that have data, BUT now I am having a problem modifying the range with each pass so that it is equal to the number of rows that do have value (found with (values[row][0] != '')). I have put a ??? in the spot where I am trying to have a variable height.
function getAllData() {
var folder = DocsList.getFolderById("folderid");
var contents = folder.getFiles();
Logger.log("file length: " + contents.length);
var file;
var data;
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Base")
sheet.clearContents();
var numOfFiles = contents.length;
for (var i = 0; i < numOfFiles; i++) {
file = contents[i];
Logger.log("count: " + i);
var theFileType = file.getFileType();
Logger.log("theFileType: " + theFileType);
if (theFileType==DocsList.FileType.SPREADSHEET) {
var sheet2 = SpreadsheetApp.open(file).getSheetByName("Sheet 1");
var lastLine = sheet2.getLastRow();
var values = sheet2.getRange('A3:J').getValues();
var formulas = sheet2.getRange('A3:J').getFormulas();
var data = [];
for(var row = 0 ; row < (values).length ; row++){
var lastrow = sheet.getLastRow()+1;
if (values[row][0] != '') {
for(var col = 0 ; col < formulas[row].length ; col++){
if(formulas[row][col] != '')
{values[row][col] = formulas[row][col]};
data.push(values[row]);}
if(data.length > 0)
sheet.getRange(lastrow, 1, ???, data[0].length).setValues(data);
}
}
};
}}
You are using getValue() as opposed to getValues() (With a letter "s" on the end)
var onecell = posheet.getRange('B4').getValue();
The documentation states:
getValue() - Returns the value of the top-left cell in the range.
The parameter for getRange() is kind of tricky and not well documented.
For example this:
getRange(2, 3, 6, 4)
gets a range from C2 to G8. Figure that out. The first number is the number 2, which is for the row 2. The second number is 3, for the third column (which is C). The third and fourth numbers are relative to the first two numbers.
Also, you are using: appendRow([array]) which uses an array for the parameter. So you must make sure that the data is in the form of an array, or use something else.
Here is the link for getValues:
Google Documentation - getValues
The example is this code:
// The code below will get the values for the range C2:G8
// in the active spreadsheet. Note that this will be a javascript array.
var values = SpreadsheetApp.getActiveSheet().getRange(2, 3, 6, 4).getValues();
Logger.log(values[0][0]);
Here is code that seems to work:
function getAllData() {
var folder = DocsList.getFolderById("Your file ID");
var contents = folder.getFiles();
Logger.log("file length: " + contents.length);
var file;
var data;
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1")
sheet.clearContents();
sheet.appendRow(["Value from Sheet One", "Range of values from Sheet Two"]);
var numOfFiles = contents.length;
for (var i = 0; i < numOfFiles; i++) {
file = contents[i];
Logger.log("count: " + i);
//Reset to null on every iteration
var onecell = null;
var theRange = null;
var theFileType = file.getFileType();
Logger.log("theFileType: " + theFileType);
if (theFileType==DocsList.FileType.SPREADSHEET) {
var sheet1 = SpreadsheetApp.open(file).getSheetByName("Sheet1");
var sheet2 = SpreadsheetApp.open(file).getSheetByName("Sheet2");
// The code below will get the values for the range A3:A9
// in the active spreadsheet. Note that this will be a javascript array.
onecell = sheet1.getRange('B4').getValue();
theRange = sheet2.getRange(1,3,1,6).getValues();
Logger.log('onecell: ' + onecell);
Logger.log('onecell[0][0]: ' + onecell[0][0]);
Logger.log('theRange: ' + theRange)
Logger.log('theRange[0][0]: ' + theRange[0][0])
var multipleValues = [theRange[0][0], theRange[0][1], theRange[0][2], theRange[0][3], theRange[0][4]];
Logger.log('multipleValues: ' + multipleValues);
sheet.appendRow([onecell, "'" + multipleValues]);
};
}
}
In the first column, it only enters one value into the sheet cell. In the second column, the cell gets multiple values put into it from the row. In other words, and entire rows values, and combined and put into one cell. I think that's what you want from the code.
If you try to put an array into a spreadsheet cell, instead of showing the array of values as text, it shows something like an object. So I put a quote in front of the values so the cell formatting would default to text.

Returning null values in Google Apps Script

The function below is returning null values the second and third time it goes through the 'for loop' and I can't figure out why. The for loop containing 'i=start' is looping through the 'decisionPoints' variable and returning data based on the the value in the first column. The first few rows in 'decisionPoints' have '1' in the first column. The next few rows have '2' in the first column and the next few have '3'. When the loop searches for '2' and '3', it returns null values for each row before the row containing the first value it's looking for. So, when it searches for '2', I see three null values for the preceding rows containing '1'. When it searches for '3', I see six null values for the preceding rows containing '1' or '2'.
Can anyone explian why?
var loBuild = function(loNumber,category){
var lo = [],
spreadsheet = SpreadsheetApp.getActiveSpreadsheet(),
decisionPointsSheet = spreadsheet.getSheetByName("Decision Points"),
lastColumn = decisionPointsSheet.getLastColumn(),
lastDecisionPoint = decisionPointsSheet.getLastRow(),
decisionPoints = decisionPointsSheet.getRange(1,1,lastDecisionPoint,lastColumn).getValues(),
count = 0,
loColumn = [];
decisionPoints.shift();
for(i in decisionPoints){
loColumn.push(decisionPoints[i][0]);
if(decisionPoints[i][0] === loNumber){
count++;
}
}
var start = loColumn.indexOf(loNumber);
for(i = start; i < count+start; i++){
lo[i] = [];
var dp = decisionPoints[i][1];
var dpLabel = decisionPoints[i][3];
for(j = 0; j < lastColumn; j++){
switch(j){
case 0:
lo[i][j] = dp;
break;
case 1:
lo[i][j] = "=countifs('" + dpLabel + "'!F:F,\"" + category + "\")"
break;
case 2:
lo[i][j] = "=countifs('" + dpLabel + "'!F:F,\"FCC\")"
break;
}
}
}
return(lo);
}
for(i = start; i < count+start; i++){
lo[i] = [];
If start=3, you begin populating your lo array from the 4th element (lo[3]) - thus lo[0], lo[1] and lo[2] will be automatically prepended into your array with null values, since arrays are 0-based.
It looks like you are trying to create a sheet row values array to replace/update existing sheet row? In that case it's probably better to make lo an array of objects containing row index reference and array of new values:
Important note: code below assumes that data in your sheet is sorted by first column (loNumber values). This assumption is based on your posted code sample, specifically how you set your count variable (you code would loop over wrong rows if sheet is not sorted by loNumber column).
lo = [];
...
var start = loColumn.indexOf(loNumber);
for(i = start; i < count+start; i++){
var objRow = {rowIndex: i, rowValues: []}; // row object
var dp = decisionPoints[i][1];
var dpLabel = decisionPoints[i][3];
for(j = 0; j < lastColumn; j++){
switch(j){
case 0:
objRow.rowValues[j] = dp;
break;
case 1:
objRow.rowValues[j] = "=countifs('" + dpLabel + "'!F:F,\"" + category + "\")"
break;
case 2:
objRow.rowValues[j] = "=countifs('" + dpLabel + "'!F:F,\"FCC\")"
break;
default:
objRow.rowValues[j] = ""; // if j is > 2
}
}
lo.push(objRow); // push row object into lo array
}
return(lo);
E.g. assuming start=3 and count=2, you will get this lo array:
[
{rowIndex:3, rowValues:[row3_col0_value, row3_col1_formula, row3_col2_formula]},
{rowIndex:4, rowValues:[row4_col0_value, row4_col1_formula, row4_col2_formula]}
]
You can then loop over lo array and setValues() in corresponding sheet row:
for ( var i=0; i<lo.length; i++ ) {
var rowData = lo[i];
sheet
.getRange( rowData.rowIndex-1, 1, 1, rowData.rowValues.length )
.setValues( [rowData.rowValues] );
}

Need help looping though 3 loops to append data in Google Scripts

I am working with Col A, B & C. Col A contains A-E, Col B Contains 1, a, 3, b, 5 and Col C will be where I will store duplicated information (a and b would go into C1 & C2). Any help would be appreciated. In summary; compare A and B for similarity, output result into C
function appendString() {
var range = SpreadsheetApp.getActiveSheet().getRange("A1:A5");
var range2 = SpreadsheetApp.getActiveSheet().getRange("B1:B5");
var range3 = SpreadsheetApp.getActiveSheet().getRange("C1:C5")
var numRows = range.getNumRows();
var x = 0
// var numCols = range.getNumColumns();
j = 1 // row A
k = 2 // row B
m = 3 // row C
n = 1
// First loop though B
for (var i = 1; i <= numRows; i++) {
// set the current value...
var currentValue = range.getCell(i, j).getValue();
// In the log tell us the current value
Logger.log("Set A:" + currentValue);
// Loops though col B to compare to col A
for (var l = 1; l <= numRows; l++) {
// Sets the current value to compare value
var compareValue = range2.getCell(l, j).getValue();
Logger.log("Set B:" + compareValue)
// If the compareValue and the currentValue (match)
if (compareValue === currentValue) {
Logger.log("MATCHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH");
// We will write the result to col C down one row
for (n; n <= x; n++) {
// this makes it only run once'
range3.setValue(currentValue);
Logger.log("Appending.................");
x = n + 3
}
}
}
}
}
I think your problem statement boils down to this: Fill column C with a list of unique values that appear in both column A and B.
There is a built-in javascript Array method Array.indexOf() that makes it very easy to search for matching elements. As the problem is defined, we want to search in a column, so to use that method we need a column to be represented as an Array. The Range.getValues() method allows us to load a whole range of values at once, and delivers them as a two-dimensional array, with rows as the first dimension. We need columns there, and we can achieve that by a matrix transposition.
So here's what we end up with. There isn't a built-in transpose(), so I've included one. As we search for matches, results are stored in an Array C, using the built-in Array.push() method. Finally, array C is treated as a two-dimensional array, transposed, and written out to the sheet in column C.
function recordMatches() {
var range = SpreadsheetApp.getActiveSheet().getRange("A1:B5");
var data = range.getValues();
// For convenience, we'll transpose the data, so
// we can treat columns as javascript arrays.
var transposed = transpose(data);
var A = transposed[0],
B = transposed[1],
C = [];
// Go through A, looking for matches in B - if found, add match to C
for (var i=0; i < A.length; i++) {
if (B.indexOf(A[i]) !== -1) C.push(A[i]);
}
// If any matches were found, write the resulting array to column C
if (C.length > 0) {
var rangeC = SpreadsheetApp.getActiveSheet().getRange(1,3,C.length);
rangeC.setValues(transpose([C]));
}
}
function transpose(a) {
return Object.keys(a[0]).map(function (c) { return a.map(function (r) { return r[c]; }); });
}

Categories