0

Imagine we have the following Sheet:

A B C D E F
1 John Juan Pepe
2 Y N Y

Then we want to iterate the row 1:

var sheet = SpreadsheetApp.getActiveSheet();

var peopleNamesRangeWithEmptyCells = sheet.getRange('A1:F1'); // ← Here are the name of the people like John, Juan...
var peopleNamesRange = [];
peopleNamesRangeWithEmptyCells.forEach(function (cell) {
   if (cell.value() != "") {
     doSomethingOnThatColumn(cell.value(), cell.getColumn());
   }
});

But it seems I cannot iterate a range with foreach as I get the error: TypeError: peopleNamesRangeWithEmptyCells.forEach is not a function

I know the common way to go for it is using getValues():

var peopleNamesRangeWithEmptyCells = sheet.getRange('A1:F1').getValues(); // ← Here are the name of the people like John, Juan...

But then I would loose the original row and column, so I could not do something like calling a function with the column as param:

doSomethingOnThatColumn(cell.value(), cell.getColumn());

It seems a basic question, but I'm struggling: How to iterate a range and not only its values?

TheMaster
  • 45,448
  • 6
  • 62
  • 85
chelder
  • 3,819
  • 6
  • 56
  • 90
  • 1
    https://stackoverflow.com/questions/63720612/what-does-the-range-method-getvalues-return-and-setvalues-accept Array has indexes. That indexes correspond to row, column numbers – TheMaster Oct 18 '22 at 15:46
  • See [Should I include tags in title?](/help/tagging) – TheMaster Oct 18 '22 at 15:54
  • @TheMaster thanks for your comment and sorry because I do not quite understand what you mean with your comment or how the answer linked can help to solve the issue. The title is slightly different to the label Apps Script VS google-apps.script, so I believe it may help. – chelder Oct 18 '22 at 16:09
  • Just trust that it'll help and read the whole thing and try to understand the linked answer – TheMaster Oct 18 '22 at 17:25
  • @TheMaster I've read it carefully, I still don't get how to get the original row and column of the sheet from the two dimensional array that getValues() return. I understand that let peopleNamesRangeWithEmptyCells = sheet.getRange('C1:F1').getValues(); will get 2D array with: John, Juan, Pepe. How to do something like: peopleNamesRangeWithEmptyCells[1].getColumn() to get the original column where John is? I mean, I understand that something like peopleNamesRangeWithEmptyCells[0][1] will point to the position [0][1] of the 2D array created, NOT the row 1 and column D of the sheet. – chelder Oct 19 '22 at 04:28
  • The original column is the starting offset+array index. You need to store the starting offset somewhere in a variable. `const rowOffset=3,columnOffset=4, D3D4 = sh.getRange(3,4,2),D3D4Values=D3D4.getValues()`. Now when you loop over rows, ``row=i+rowOffset+1``, where `i` is the outer index. – TheMaster Oct 19 '22 at 04:33
  • A solution may be creating the array of values of the whole column so the position of the array coincide with the position of the column of the sheet (considering that the position 0 of the array is the the column B). It doesn't seem the most elegant solution though! Or maybe a map including the value and it's original row and column on the sheet. But this question is to know a more "built-in" way if any. I'm thinking but not getting any better idea to explain my question. Thanks for your attempts to help!!! – chelder Oct 19 '22 at 04:36
  • I'm following the idea of getting the whole row, so the array of getValues() coincide with the column. I'm following this: https://yagisanatode.com/2017/12/13/google-apps-script-iterating-through-ranges-in-sheets-the-right-and-wrong-way/ – chelder Oct 19 '22 at 04:44
  • Check my answer. You just need to add the initial offset – TheMaster Oct 19 '22 at 04:45

3 Answers3

1

The original column is the starting offset+array index. You need to store the starting offset somewhere in a variable.

const rowOffset=3,
    columnOffset=4,
    D3E4 = sh.getRange(3,4,2,2),
    D3E4Values=D3E4.getValues();

Now when you loop over rows, the original row is equal to i+rowOffset+1, where i is the outer index. For eg,

D3E4Values.forEach((row,i) => {
  row.forEach((col,j) => {
    console.log(`The original row of this value ${col} is ${i + rowOffset + 1}`);
    const currentColumn = j + columnOffset + 1;
    console.log(`The original column of value ${col} is ${currentColumn}`)
    //equivalent to doSomethingOnThatColumn(cell.getValue(),cell.getColumn())
    doSomethingOnThatColumn(col, currentColumn);
  })
})
TheMaster
  • 45,448
  • 6
  • 62
  • 85
  • I've written an answer mixing all the details learnt from you and "Google". I thought it would be something obvious... But not at all! – chelder Oct 19 '22 at 16:26
  • @chelder Great. If you've used anything from my answer, just make sure to give appropriate credit. For more details, see [referencing help](/help/referencing) and [this FAQ](https://meta.stackexchange.com/q/160077/343832). – TheMaster Oct 19 '22 at 16:35
0

I don't know of any way to iterate a Range object. And I don't understand your comment "But then I would loose the original row and column".

Here is an example of how to iterate through the first row.

function test() {
  try {
    let spread = SpreadsheetApp.getActiveSpreadsheet();
    let sheet = spread.getSheetByName("Sheet2");
    let peopleNamesRangeWithEmptyCells = sheet.getRange('A1:F1').getValues()[0]; // [0] since its a single row
    let peopleNamesRange = [];
    peopleNamesRangeWithEmptyCells.forEach( cell => {
        if( cell !== '' ) peopleNamesRange.push(cell);
      }
    );
    console.log(peopleNamesRange);
  }
  catch(err) {
    console.log(err);
  }
}

Execution log

9:13:49 AM  Notice  Execution started
9:13:50 AM  Info    [ 'John', 'Juan', 'Pepe' ]
9:13:50 AM  Notice  Execution completed

Here is an example of the same thing with a traditional for loop. peopleNamesRangeWithEmptyCells.length is the number of rows and peopleNamesRangeWithEmptyCells[0].length is the number of columns

function test() {
  try {
    let spread = SpreadsheetApp.getActiveSpreadsheet();
    let sheet = spread.getSheetByName("Sheet2");
    let peopleNamesRangeWithEmptyCells = sheet.getRange('A1:F1').getValues();
    let peopleNamesRange = [];
    for( let i=0; i<peopleNamesRangeWithEmptyCells.length; i++ ) {  // do for each row
      for( let j=0; j<peopleNamesRangeWithEmptyCells[0].length; j++ ) {  // do for each column
        if( peopleNamesRangeWithEmptyCells[i][j] !== '' ) {
          peopleNamesRange.push(peopleNamesRangeWithEmptyCells[i][j]);
        }
      }
    }
    console.log(peopleNamesRange);
  }
  catch(err) {
    console.log(err);
  }
}

Execution log

10:45:46 AM Notice  Execution started
10:45:47 AM Info    [ 'John', 'Juan', 'Pepe' ]
10:45:47 AM Notice  Execution completed

Finally how to get the column associated with one of the names.

function test() {
  try {
    let spread = SpreadsheetApp.getActiveSpreadsheet();
    let sheet = spread.getSheetByName("Sheet2");
    let peopleNamesRangeWithEmptyCells = sheet.getRange('A1:F1').getValues()[0]; // [0] since its a single row
    let peopleNamesRange = [];
    peopleNamesRangeWithEmptyCells.forEach( cell => {
        if( cell !== '' ) peopleNamesRange.push(cell);
      }
    );
    console.log(peopleNamesRange);
    let name = "Juan";
    let column = peopleNamesRangeWithEmptyCells.indexOf(name)+1;
    console.log("column = "+column);
    console.log("column = "+String.fromCharCode(64+column));
  }
  catch(err) {
    console.log(err);
  }
}

Execution log

3:00:42 PM  Notice  Execution started
3:00:43 PM  Info    [ 'John', 'Juan', 'Pepe' ]
3:00:43 PM  Info    column = 5
3:00:43 PM  Info    column = E
3:00:43 PM  Notice  Execution completed
TheWizEd
  • 7,517
  • 2
  • 11
  • 19
  • I've added to the example code the call to a function: `ddoSomethingOnThatColumn(cell.value(), cell.getColumn());`. I hope that can help to understand the question. If in your code by using `.getValues()[0]` you're iterating the values instead the whole range, how can you call that function if you only have the value, but not the row and column where that value is on the sheet? – chelder Oct 18 '22 at 16:57
  • I don't think you understand how Google Spreadsheet works. The spreadsheet resided on a server. Your browser is a client that makes request to the server to edit the spreadsheet. What Google App Script is is a way to send request to the server for data. Your javascript function manipulates that data and then sends the modified data back to the server and the browser view is updated. You use `getValue()/getValues()` to get the data into and array and the `setValue()/setValues()` to send the modified data back. – TheWizEd Oct 18 '22 at 17:08
  • `getValues()` gets a 2D array which is typically indexed values[i][j]. I know that i=0 is row 1 and j=0 is column 1, so values[2][3] is row 3 column 4. – TheWizEd Oct 18 '22 at 17:11
  • I understand that `let peopleNamesRangeWithEmptyCells = sheet.getRange('A1:F1').getValues();` will get 2D array with: John, Juan, Pepe. How to do something like: `peopleNamesRangeWithEmptyCells[1].getColumn()` to get the original column where John is? (Which not sure if 3 or 4 after your previous comment). – chelder Oct 18 '22 at 18:25
0

getValues() does NOT include the position of the value on the sheet as it's just an 2D array containing the values of the given range.

A way to go would be to create the array containing the whole sheet (I got the idea from here):

  var sheet = SpreadsheetApp.getActiveSheet();
  var rangeData = sheet.getDataRange();
  var lastColumn = rangeData.getLastColumn(); 
  var lastRow = rangeData.getLastRow();
  var searchRange = sheet.getRange(1, 1, lastRow, lastColumn);
  var rangeValues = searchRange.getValues();

So then we can iterate the 2D array of values, and because it contains the whole sheet, the position of a value at the 2 array of values is the same than the sheet... With an important detail: the array start by [0][0] whereas the sheet starts by (1)(A):

  for ( i = 0; i < lastColumn - 1; i++){
    for ( j = 0 ; j < lastRow - 1; j++){
      
      currentColumnInOriginalSheet = i+1;
      currentValueAtThisForLoop = rangeValues[j][i];          
      doSomethingOnThatColumn(currentColumnInOriginalSheet, currentValueAtThisForLoop);

    };
  };

If we don't want to include the whole sheet on the 2D array of values, then we can use a variable (normally called offset) to indicate how many positions we have to add to calculate the position in the original sheet.

So if we want to iterate the values from the column D to column F, we create a variable named offset with the number of columns from A to D (4 positions):

  var columnOffset = 4;
  var searchRange = sheet.getRange(1, columnOffset, lastRow, lastColumn);

  for ( i = columnOffset; i < lastColumn - 1; i++){
    for ( j = 0 ; j < lastRow - 1; j++){
      
      currentColumnInOriginalSheet = i+1;
      currentValueAtThisForLoop = rangeValues[j][i-columnOffset];          
      doSomethingOnThatColumn(currentColumnInOriginalSheet, currentValueAtThisForLoop);

    };
  };
chelder
  • 3,819
  • 6
  • 56
  • 90