Iterate through column in Excel and marking it in a different cell - javascript

I'm fairly new to JavaScript/TypeScript and I'm trying to to iterate through a column of my excel spreadsheet and if the cell contains the word "leave" have the output be text ("false negative") in a cell on the same row 8 columns over.
function main(workbook: ExcelScript.Workbook) {
// Get the current worksheet.
let selectedSheet = workbook.getActiveWorksheet();
// Get the values of the used range.
let range = selectedSheet.getUsedRange();
let rangeValues = range.getValues();
// Iterate over the Query column.
let rowCount = range.getRowCount();
for (let i = 1; i < rowCount; i++) {
// Query is column "3" in the worksheet and indexed at 2.
if (rangeValues[i][2].includes("leave")) {
range.getCell(i, 10).setValue("false negative");
}
}
}
I get these errors
Line 13: rangeValues[i][2].includes is not a function
[13, 27] Property 'includes' does not exist on type 'string | number | boolean'.
Property 'includes' does not exist on type 'number'.

To make the minimal code changes to your code, you can construct a new String from the value.
String(rangeValues[i][2]).includes("leave")

Related

Retrieving all values in a column

I am new to google apps script and I was trying to get all the values in a particular column inside a sheet named "Items". I was able to create a loop to get to the last row that contains value but when I try to use the function, no data is retrieved. I tried console.log(values[lr][0]); inside the if clause and it outputs just fine.
Here's my code
function getAllItems()
{
var ss= SpreadsheetApp.getActiveSpreadsheet();
var locationSheet = ss.getSheetByName("Items");
var values = locationSheet.getRange("Items!B2:B").getValues();
for(var i = values.length - 1 ; i >= 0 ; i--){
if (values[i][0] != null && values[i][0] != ""){
lr = i + 1;
values.sort();
return values[lr][0];
}
}
}
There are several ways to retrieve values from a column in Google Sheets.
The basics, getting the sheet
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var sheet = spreadsheet.getSheetByName('Items');
SpreadsheetApp.getActiveSpreadsheet() works in bounded projects and add-ons. Spreadsheet.getSheetByName(name) works when the sheet name is known.
Getting the column values by using Sheet.getRange and an open reference using A1 notation
var values = sheet.getRange('B:B').getValues();
If your spreadsheet has blank rows at the bottom, in this case Range.getValues besides the column values, it will return an empty string for each blank rows.
Besides using Sheet.getRange with an open reference, it might be used with other reference types and using start row, start column, number of rows and number of columns.
Getting the column values by using Sheet.getRange and an open reference using A1 notation excluding empty strings
var values = sheet.getRange('B:B').getValues().filter(String);
Getting the column values by using Sheet.getDataRange and Array.prototype.map
var values = sheet.getDataRange().getValues().map(row => row[1]);
Only will return the values from the first row to the last row of the data range. The data range is determined from A1 to the last row and last column having values, i.e., if one column B have values from row 1 to row 10 and column C have values from row 4 to row 20, the data range reference is A1:C20, so values will contain the values from row 1 to row 20, showing empty strings for the blank cells.
Getting the column values by using Sheet.getDataRange, Array.prototype.splice and Array.prototype.getLastIndex
var values = sheet.getDataRange().getValues();
values.splice(values.findLastIndex(String) + 1);
Only will return the values from the first row to the last row of the column containing non empty strings. This might be helpful when having columns "of different sizes", as explained in the previous case. Please note that if there blank cells in between, an empty string will be included as value of these cells.
Notes:
Instead of Range.getValues you might use Range.getDisplayValues to get the strings with the values formatted as strings as they are displayed on Google Sheets cells. Both methods return the values structured as an Array of Arrays, this might be handy if you will be adding the values to another range, but if you want to add them to the execution logs you might want to format them in another way.
Please bear in mind that if the column content is very large, nowadays a Google Sheets spreadsheet could have up to 10 million cells and each cell could have upto 50k characters, the column content will be truncated when printed to the execution logs.
Related
Get column from a two dimensional array
Resources
Array
You don't need a loop for that (explanation in comments):
function getAllItems()
{
var ss= SpreadsheetApp.getActiveSpreadsheet();
var locationSheet = ss.getSheetByName("Items");
var values = locationSheet.getRange("Items!B2:B").getValues().flat(); // 2D -> 1D array
var filter_values = values.filter(r=>r!=''); // remove empty rows
Logger.log(filter_values); // get the full list
Logger.log(filter_values[filter_values.length-1]); // get the last value;
return filter_values[filter_values.length-1];
}
Try this:
function getAllItems(){
var ss= SpreadsheetApp.getActive();
var sh = ss.getSheetByName("Items");
var vs = sh.getRange("B2:B"+sh.getLastRow()).getValues();//all the values in column B
return sh.getLastRow();//the last row with data
}
Or you can use:
function getColumnHeight(col, sh, ss) {
var ss = ss || SpreadsheetApp.getActive();
var sh = sh || ss.getActiveSheet();
var col = col || sh.getActiveCell().getColumn();
var rcA = [];
if (sh.getLastRow()){ rcA = sh.getRange(1, col, sh.getLastRow(), 1).getValues().flat().reverse(); }
let s = 0;
for (let i = 0; i < rcA.length; i++) {
if (rcA[i].toString().length == 0) {
s++;
} else {
break;
}
}
return rcA.length - s;
//const h = Utilities.formatString('col: %s len: %s', col, rcA.length - s);
//Logger.log(h);
//SpreadsheetApp.getUi().showModelessDialog(HtmlService.createHtmlOutput(h).setWidth(150).setHeight(100), 'Col Length')
}
function getAllItems(){
var ss= SpreadsheetApp.getActive();
var sh = ss.getSheetByName("Items");
var vs = sh.getRange("B2:B"+getColumnHeight(2,sh,ss).getValues();//all the values in column B
return sh.getLastRow();//the last row with data
}
If you use filter() to filter out all of the nulls you may not get the desired result if one of the data elements is null.

What is the correct structure of a 2D array in GAS that is used to create a CheckboxgridItem response in a Google Form

The previous "answer" that I have been referred to does not solve this problem. I tried that technique before I posted here. That technique did not work and I received the same error as the one I'm receiving now.
I am trying to create a Google Apps Script that will allow me to use previous data from a Form and "resubmit" the data into a Form. The use case is that I will have teachers who change Form questions after they have received submissions from sections of their class. The change will cause the previous data to no longer be included in the Form response summary page or the individual responses. Instead the data is hidden from view and only accessible through downloading the csv of the responses or the data found in the linked Sheet (provided the teacher linked the Sheet before editing the Form). The current work around is to use the Form's Pre-Filled-URL feature and then create a URL for each row of data through the use of formulas in the Sheet. The teacher must then manually click on each link to enter the data back into the Form. This is not only time consuming but many of the teachers cannot create a formula that will create the individual url links.
To solve this, I'm creating a script that I can use with these teachers when this occurs. I have everything figured out except for the grid and time items. Here's a stripped down version of the script that applies only to a checkbox_grid question.
The data is in one column (B1:B) like this:
1X2 Grid [R1]
C1, C2
C1
C2
C1, C2
In the script, I've been able to create an array for each cell that looks like:
array[0] = [['C1'].['C2']]
array[1] = [['C1'],['']]
array[2] = [[''],['C2']]
array[3] = [['C1'].['C2']]
The following error is being returned:
Exception: Wrong number of responses: 2. Expected exactly: 1.
Also, while researching this further, I found the following in the developers site:
For GridItem questions, this returns a String[] array in which the answer at index n corresponds to the question at row n + 1 in the grid. If a respondent did not answer a question in the grid, that answer is returned as ''.
For CheckboxGridItem questions, this returns a String[][] array in which the answers at row index n corresponds to the question at row n + 1 in the checkbox grid. If a respondent did not answer a question in the grid, that answer is returned as ''.
What is the correct way to structure the array so that GAS will accept it?
Code found in sample file is here:
//Global Variables for Spreadsheet
const ss = SpreadsheetApp.getActiveSpreadsheet();
var dataSheet = ss.getSheetByName('Data');
var lastRow = dataSheet.getLastRow();
var lastCol = dataSheet.getLastColumn();
var sheetHeader = dataSheet.getRange('1:1');
var sheetTitles = sheetHeader.getValues();
var error = 'None';
var cellValue = [];
//Global Variables for Form
var form = FormApp.openByUrl(ss.getFormUrl());
var formResponses = form.getResponses();
var formCreate = form.createResponse();
var formItems = form.getItems();
//sendValues() calls the setResponses() to set the values of each form response.
// The function then submits the forms and clears the responses.
// Flush is used to make sure the order of the code is retained.
//------------------ New Variables -------------------------------
// r = row
function sendValues() {
for (r = 2; r <= lastRow; r++) {
setResponses(r);
//formCreate.submit();
console.log('submitted');
formCreate = form.createResponse();
SpreadsheetApp.flush();
}
}
//setResponses(r) sets the response for each cell of the data sheet.
//calls gridResponse(cell,i) if the question type is either a checkbox grid or multiple choice grid question.
//----------------------- New Variables ---------------------------
// c = column
// i = item response
// ssHeaderValue = Values of the Row 1 in the Data Sheet (dataSheet)
// cell = Value of each cell in dataSheet
// cellValue = Converts commas in cell variable to ;
// formItem = each item from all of the Form items
// fHeaderValue = Value of each "header" (Title) of each Form value
// itemAs = item set to correct type
// itemResponse = created response
function setResponses(r) {
for (c = 2; c <= lastCol; c++) {
for (i = 0; i < formItems.length; i++) {
var ssHeaderValue = dataSheet.getRange(1, c).getValue();
var itemType = formItems[i].getType();
var cell = dataSheet.getRange(r, c).getValue();
gridResponse(cell, i);
var formItem = formItems[i];
var fHeaderValue = formItem.getTitle();
var itemResponse = formItem
.asCheckboxGridItem()
.createResponse(cellValue);
}
//ERROR HERE: formCreate.withItemResponse(itemResponse);
}
}
//checkboxGridResponse(cell,i) makes an array of cellValue that can be used in CHECKBOX_GRID item.
//--------------------- New variables ------------------------------
// z = loop counter
// gridColumns = number of possible responses in the Form
// cellValueLength = number of responses in the cell data
// cellColumns = cell data split into separate values
// cellValue = value to be returned
// arr = temporary array for making cellValue into a 2D array
function gridResponse(cell, i) {
var gridColumns = formItems[i].asCheckboxGridItem().getColumns();
var cellValueLength = cell.split(',').length;
var cellColumns = cell.split(',');
for (z = 0; z < cellValueLength; z++) {
console.log(
'cellColumns ' +
z +
' = ' +
cellColumns[z] +
'; gridColumns ' +
z +
' = ' +
gridColumns[z]
);
var arr = [gridColumns[z] == cellColumns[z] ? gridColumns[z] : null];
cellValue.push(arr);
}
console.log(cellValue);
console.log('cellValue[0][0] = ' + cellValue[0][0]);
console.log('cellValue[0][1] = ' + cellValue[0][1]);
console.log('cellValue [1][0] = ' + cellValue[1][0]);
return cellValue;
}
The structure of a response for a CheckboxGridItem is a 2D array:
Outer array should contain row array
Inner array should contain column elements
The elements themselves should be the column labels
This sample script for a 1 item form submits a 2x2 CheckboxGridItem array choosing "Column 1" for both rows.
Column 1
Column 2
✅
⬜
✅
⬜
function myFunc16() {
const form = FormApp.getActiveForm();
const newRes = form
.getItems()[0]
.asCheckboxGridItem()
.createResponse([['Column 1'], ['Column 1']]);
form.createResponse().withItemResponse(newRes).submit();
}
To submit another response choosing
Column 1
Column 2
⬜
⬜
✅
⬜
the array should be:
[
null, //Row1
['Column 1'] //Row2
]
Similarly, for
Column 1
Column 2
⬜
✅
⬜
⬜
Array:
[
['Column 2'],
null
]
If you insert a array
[
['Column 2']
]
for a 2x2 CheckboxGridItem, the error,
Exception: Wrong number of responses: 1. Expected exactly: 2.
is thrown. It means the array.length doesn't match the number of rows in the grid.
The structure of array can be found by manually submitting the 1 checkbox grid item form and examining the response programmatically using:
//Examine third response [2]
//First item's response [0]
console.log(form.getResponses()[2].getItemResponses()[0].getResponse())

Get a value and row number in selected range with gaps between cells?

I need to get values and row number in Google sheet script out of sellected range.
Original problem was that I was trying to get values and row numbers of sellected range using this script:
sheet = ss.getActiveSheet(),
sheetvalues = sheet.getActiveRange().getValues();
for (i=0; i<sheetvalues.length; i++) {
mr = sheet.getActiveRange().getRow()+i;
}
But I found out that if you use filer and select a range then all the hidden cells will also be part of the activerange, but I need only those that are inside of a filtered range. I decided that the best way is to select cells separately one by one.By I can't get a value and row index cause it counts as active range only the last selected cell.
I believe your goal as follows.
Under the selected rows for the sheet, you want to retrieve the row numbers of no selected rows using Google Apps Script.
In this case, I think that the method of getSelection can be used. And, in order to confirm the rows, getActiveRangeList is used. When this is reflected to a sample script, it becomes as follows.
Sample script:
When you use this script, at first, please select the cells like your sample image and run the script.
function myFunction() {
// 1. Retrieve the selected ranges.
const selection = SpreadsheetApp.getSelection();
// 2. Retrieve the selected row numbers.
const selectedRows = selection.getActiveRangeList().getRanges().flatMap(r => {
const row = r.getRow();
const temp = [];
for (let i = 0; i < r.getNumRows(); i++) {
temp.push(row + i);
}
return temp;
}).sort((a, b) => a - b);
// 3. From data range, retrieve the row numbers except for the selected row numbers.
const sheet = selection.getActiveSheet();
const range = sheet.getDataRange();
const rowNumbers = [];
for (let r = 1; r <= range.getNumRows(); r++) {
if (!selectedRows.includes(r)) rowNumbers.push(r);
}
console.log(rowNumbers); // Here, you can see the retrieved row numbers at the log.
// 4. Retrieve the row values of the retrieved row numbers.
// If you want to retrieve the row values of "rowNumbers", you can also the following script.
const allValues = range.getValues();
const values = rowNumbers.map(n => allValues[n]);
console.log(values); // Here, you can see the values of row numbers at the log.
}
As the important point for using the selected range, for example, when the cells of row 3, row 1, row 2 are selected in order, the range list returns the order of cells. So in this sample script, the retrieved row numbers are sorted.
When above script is run, for example, when the data range is "A1:C5" and when the cells "A1", "A3" and "A5" are selected, the row numbers of 2, 4 are retrieved.
References:
getSelection()
getActiveRangeList()

How do I run through rows and fetch the value of a cell, if another cell is true?

In Google Sheets, I have X rows with data in 10 columns. The first row contain headers.
I need to run through every row in my current sheet, check column B, in the current row, and if the value of that cell is "FOUND", then I want to fetch the value of the cell in column C, in the current row.
I have this experimental code, but it doesn't work properly, as it returns all incidences regardless of values:
function curCellValue() {
var sheet = SpreadsheetApp.getActiveSheet();
var data = sheet.getDataRange().getValues();
for (var i = 1; i < data.length; i++) {
if (data[i][1] = 'FOUND'){
Logger.log(data[i][2]);
}
}
}
You have a typo: the line below always returns true, because it is assigning:
if (data[i][1] = 'FOUND'){
Change the assignment to a check ===. Then you'll find rows that are FOUND.

Google Apps Script inserts a date to GSheets but changes to undefined

This code will insert the correct dates in the correct cells, but seems like after each loop (before moving to the next row) the date changes to undefined. This only happens for var = expDelivery all other dates are fine.
I am not sure why it inserts the date, correctly, then changes to undefined.
function doPost(e){
//append the ID to your spreadsheet like this:
var sheetID = e.parameter.sheetid;
var ss = SpreadsheetApp.openById(sheetID);
var sh = ss.getSheetByName("Sheet1");
var headers = sh.getRange(1,1,1,sh.getLastColumn()).getValues()[0];
var uninstalledCol = headers.indexOf("uninstalledDate");
//get all variables passed from app
var ft = e.parameter.ftNumber;
var measuredDate = new Date(e.parameter.measuredDate);
var installedDate = new Date(e.parameter.installedDate);
var uninstalledDate = new Date(e.parameter.uninstalledDate);
//add 21 days to measuredDate
var dayInMs = 24*60*60*1000; //one day in Milliseconds
var daysToAdd = 21;
var expDelivery = new Date(measuredDate.getTime()+(daysToAdd*dayInMs));
var shtData = sh.getDataRange();
var shtVals = shtData.getValues();
var updatedCols = [];
for(var j = 1; j < shtVals.length; j++) //Loop through rows
{
for (var i = 6; i < uninstalledCol; i++) //Loop through columns
{
// IF col 1 of current row = ft AND current cell is blank AND current col title = measuredDate AND value passed with same name as col title IS NOT blank
if(shtVals[j][0] == ft && shtVals[j][i] == "" && headers[i] == "measuredDate" && e.parameter[headers[i]] != "")
{
shtVals[j][i] = e.parameter[headers[i]];
shtVals[j][i+1] = expDelivery; //Will set the correct date in spreadsheet but changes to "undefined" before moving to next row
shtData.setValues(shtVals);
updatedCols.push([headers[i]]);
}
// IF col 1 of current row = ft AND current cell is blank AND current col title IS NOT "DELIVERED DATE" or "measuredDate" AND value passed with same name as col title IS NOT blank
else if(shtVals[j][0] == ft && shtVals[j][i] == "" && headers[i] != "DELIVERED DATE" && headers[i] != "measuredDate" && e.parameter[headers[i]] != "")
{
shtVals[j][i] = e.parameter[headers[i]];
shtData.setValue(shtVals);
updatedCols.push([headers[i]]);
}
}
}
return message(updatedCols);
}
function message(msg) {
return ContentService.createTextOutput(JSON.stringify({Result: msg })).setMimeType(ContentService.MimeType.JSON);
}
Your code is so inefficient. You're reading sheet values onece here var shtVals = sh.getDataRange().getValues();. Then for each row, for each col, you're reading and writing sheet values again and again.
for(var j = 1; j < shtVals.length; j++) //Loop through rows
{
for (var i = 6; i < uninstalledCol; i++) //Loop through columns
{
{
// do stuff
}
{
// do stuff
}
}
}
shtData.setValues(data);
Reading and writing takes time, that's why you should minimize them as possible. Best way is to read once, do all operation on the values then write once.
There can always be special cases and maybe you don't need speed.
About the issue -
It's writing here data[j][i+1] = expDelivery;. This is inside the inner loop which is running a few times, as suggested by the code. The code seems modifying col by col of a row, so it is unlikely for a row to mess with previous row. This is probably inner loop issue where cols are getting read, manipulated and written.
The loop code is not easy to understand, so, fixing bug is harder. You might wanna simplify it first if you haven't fixed the issue already.
I need the values to go to specific cells, is it possible to do this with a single write function?
Yes.
Lets say this is your sheet -
-----------------------------
| A | B |
-----------------------------
1 | Name | Age |
-----------------------------
2 | Alice | 25 |
-----------------------------
3 | Bob | 30 |
-----------------------------
We do var values = sheet.getDataRange().getValues(). We get -
values = [ [ Name, Age ],
[ Alice, 25 ],
[ Bob, 30 ] ]
Let's say you want to change A2 into Jane. Which indices do you use to access it inside values array ?
You can see from sheet, its 2nd row, 1st col. But values is a 2-dimensional array, the indices will be
values[1][0] because indices in arrays start with 0.
So, the actual indices to access A2 inside values will be values[1][0], row and col each 1 less than sheet row, col indices.
Lets change A2 to Jane. values[1][0] = 'Jane'
Once you change values array, this change is live, in memory, ready to be accessed in other operation or calculation. But, it is not written to sheet yet.
Lets say you need to change B2 as Jane is 26. So, do you make that change now ? Or, write previous change into sheet first ? It is preferred to make that change now and write both changes in one operation.
Now, what is the indices in values for B2?. B2 is 2nd row, 2nd col in sheet. In values array it is values[1][1].
Lets change B2 to 26. values[1][1] = 26. Now both changes are live. If we write values into sheet, both changes will be saved.
The part that is giving me trouble when it comes to this, is specifying the range to set values, how do I pass the [j] and [I] values of each entry.
Now you know how sheet indices and values indices work. Use values indices to change/set values on it then write values into sheet.

Categories