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] );
}
Related
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.
I have written that has a for loop that will run additional code if (sheetData[i] !== "").
Ideally, I would like to run the following code after the condition, but then loop back around and run it again for the next item that matches the condition. Any ideas on how I can achieve this?
function getSheetSectionDataTest(){
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Params"); // get sheet
var sheetData = sheet.getDataRange().getValues(); //get all sheet data
var sectionNames = normalizeHeaders(normalizeHeaders(sheet.getRange('A1:A').getValues()));
var sectionData = []; // main array to contain all section data
// create an array for each section
for(h = 0; h < sectionNames.length; h++) {
sectionData[sectionNames[h]] /*property name or key of choice*/
= [];
}
for (var i = 0; i < sheetData.length; i++){ //loop through each row in the spreadsheet
var sectionName = normalizeHeaders(sheetData[i]); //return normalized camelCase section name found in column A.
if (sheetData[i] !== ""){ // Test - stop at a cell that matches the criteria and return the data table.
var headerRow = normalizeHeaders(sheetData[i+1]); //define and normalize the table headers
for (var j = i+2; j < sheetData.length; j++) { //loop through each row in the data table.
if (sheetData[j][1] !== ""){ //if there are contents in the table keep looping.
var obj = {};
sectionData[sectionName[0]].push(obj); // Need to replace ranges with a dynamic variable that gives us the current sectionName value as an object?
for (var rowColumn = 0; rowColumn < headerRow.length; rowColumn++) { //loop through each column in the current row of the table.
obj[headerRow[rowColumn]]=sheetData[j][rowColumn+1];
}
}
else { //stop when an empty cell is reached
return sectionData; //when the data table loop runs into an empty cell stop loop and return the data
}
}
}
}
};
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.
I have dozens of spreadsheets thousands of rows long and I want to subset them by deleting rows that do not satisfy a condition.
Let me put forward a simplified example. Say Row C has string values for department names at a university (eg "ANTHRO" is Anthropology, "ART-HIST" is Art History, and so on). The university has many departments and the spreadsheet has many entries for each department, but I only want data for Anthropology and Art History. Therefore my task is to write a script that deletes each row that does not satisfy the condition RowC = "ANTHRO" or "ART-HIST".
Problem is, I don't know how in javascript/google-apps-script to define a variable that takes a range of (string) values. One attempt saw me define a "cull" variable as an array containing the conditions the script will judge the data on:
var rowsDeleted = 0;
var keep = ["ANTHRO", "ART-HIST"];
for (var i = 0; i <= numRows - 1; i++) {
var row = values[i];
if (row[2] != keep) {
sheet.deleteRow((parseInt(i)+1) - rowsDeleted);
rowsDeleted++;
}
}
};
Yet it did not work. I know I could simply write if(row[2] != "ANTHRO" || != "ART"), but in reality there are much more than two conditions. Defining the so-called "cull" variable seems more efficient.
Any insights as to why the array-approach did not work? Thank you.
You could try using indexOf. If the row value isn't inside the array, it will return a value of -1, otherwise will return the index.
var rowsDeleted = 0;
var keep = ["ANTHRO",
"ART-HIST"];
for (var i = 0; i <= numRows - 1; i++) {
var row = values[i];
if (keep.indexOf(row[2]) === -1) {
sheet.deleteRow((parseInt(i)+1) - rowsDeleted);
rowsDeleted++;
}
}
};
To me, what you're trying to do really is to filter a collecion of data based on speicific conditions. First you can store thousands rows of data into an array, and each element of the array can be a object to represent a row of many columns. And then you can filter the array by condition that whether a row contains string "ANTHRO" or "ART-HIST". A possible code implementaiton is:
var data = [
{ DEPT_ID: 1, ABBR_NAME: "ANTHRO", FULL_NAME: "Anthropology", StudentsNO: 240 },
{ DEPT_ID: 2, ABBR_NAME: "ART-HIST", NAME: "Art History", StudentsNO: 200 },
{ DEPT_ID: 3, ABBR_NAME: "MATH", FULL_NAME: "Mathematics", StudentsNO: 50 },
{ DEPT_ID: 4, ABBR_NAME: "CS", NAME: "Computer Science", StudentsNO: 79 }
];
function isAnthroOrArtHist(element) {
return ["ANTHRO", "ART-HIST"].indexOf(element.ABBR_NAME) >= 0;
}
var newData = data.filter(isAnthroOrArtHist);
I sort of do the same thing where I pull a list of Liquor Inventory to another tab along with a VENDOR name and if the VENDOR name isn't listed, then it hides the row
Here's what I used. You might be able to tweak it to where you need.
function liquorOrderGuideWorking() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('LIQUOR ORDER');
// Rows start at "1" - this will delete the first two rows
//sheet.deleteRows(2, 567);
var ss = SpreadsheetApp.getActiveSpreadsheet();
var liquorInventory = ss.getSheetByName('LIQUOR INVENTORY');
var liquorInventoryRange = liquorInventory.getRange('B6:C573'); //Holds Vendor & Item Name
var liquorInventoryTotalRange = liquorInventory.getRange('I6:I573'); //Holds QTY of each item
var liquorOrder = ss.getSheetByName('LIQUOR ORDER'); //Gets the new sheet
var liquorOrderRange = liquorOrder.getRange('A2:B569'); //Places Vendor & Item Name
var liquorOrderQTYRange = liquorOrder.getRange('C2:C569'); //Places QTY
liquorInventoryRange.copyTo(liquorOrderRange, {contentsOnly:true})
liquorInventoryTotalRange.copyTo(liquorOrderQTYRange, {contentsOnly:true})
var s = ss.getSheetByName('LIQUOR ORDER');
var lastCol = s.getLastColumn();
var lastRow = s.getLastRow();
// assumes headers in row 1
var r = s.getRange(2, 1, lastRow - 1, lastCol);
// Note the use of an array
r.sort([{ column: 1, ascending: true }, { column: 2, ascending: true}]);
var ssHide = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ssHide.getSheetByName('LIQUOR ORDER');
var maxRows = sheet.getMaxRows();
//show all the rows
sheet.showRows(1, maxRows);
//get data from column B
var data = sheet.getRange('A:A').getValues();
//iterate over all rows
for(var i=0; i< data.length; i++){
//compare first character, if blank, then hide row
if(data[i][0].charAt(0) == ''){
sheet.hideRows(i+1);
}
}
Browser.msgBox('Liquor Order Guide','The Liquor Order Guide has been refreshed successfully.', Browser.Buttons.OK);
}
I have a sheet like this:
And I have this function:
function getRangeAsArrays(sheet) {
var dataRange = sheet.getRange(2, 1, sheet.getLastRow(), sheet.getLastColumn());
var data = dataRange.getValues();
var array = [];
for (var r=0; r<sheet.getLastColumn(); r++) {
for (i in data) {
var row = data[i];
array.push(row);
}
}
return array;
}
Which I use to build a listboxthis way:
var recipientArray = getRangeAsArrays(activeSheet);
var item3Panel = app.createHorizontalPanel();
item3Panel.add(app.createLabel("recipient"));
var listBox = app.createListBox().setName('item3');
for(var i = 0; i < (recipientArray.length); i++){
Logger.log("recipientArray[i][2] = " + recipientArray[i][3]);
Logger.log(" i = " + i);
listBox.addItem(recipientArray[i][4]);
}
item3Panel.add(listBox);
But when I iterate over the array length (4 rows), I got this (unexpected to me) result and the logs shows i variable goes until 14:
Since recipientArray.length should give me the first dimension of the 2 dimensional array and recipientArray[i].length the second dimension, and since I want the first dimension (number of rows) row to fix that? What is going wrong here?
Even if I'm still unsure that I understood what you need (I guess I'm a bit tired or I become stupid... go figure...:), I wonder why you try using separate functions since the value returned by range.getValues() is already an array... A 2D array but still an array.
If you want to create one listBox per row and add the following cells as items then a double loop like this will do the job. (tell me if I'm completely off the subject, thx).
I wrote an example code with the main structure and comments to explain where things go.
function test() {
var dataRange = SpreadsheetApp.getActiveSheet().getRange(2, 1, sheet.getLastRow(), sheet.getLastColumn());
var data = dataRange.getValues();
Logger.log(data)
var array = []; // this is useless
for (var r=0; r<data.length; r++) {
// create listBox widget here
//var listBox = app.createListBox().setName('listBox'+r);
for (i in data[0]) {
var cell = data[r][i];
//add items to listBox here
// listBox.addItem(cell);
array.push(cell);// this is useless
}
}
Logger.log(array);//useless
return array;//useless
}