I am trying to all the data from a range in a spreadsheet and filter it based on a certain condition where item[27] and item [29] equals "CO". It's working fine and filtering the range properly however I am trying to output only one item in the array which is item[3] and it's not doing that. It's giving me the filtered version of the entire range which is good but I don't want that.
This is basically a spreadsheet containing students and information about the work modules they have completed. I want to filter only the names of the students which is item[3] who have a "CO" or "complete" marked against a certain work module into another sheet. I tried to map the filtered data in another array using data.map and then tried to output just one element of the array and that is not working as well.
BTW this is my first time coding anything and using Google Apps script. I am really interested in this stuff and hopefully, one day be half as good as anyone here. Any help will me much appreciated.
function ReviewReport() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var mastersheet = ss.getSheetByName("Master Sheet 18-19-20");
var newtab = ss.getSheetByName("ReviewReportTest(Amith)");
var totalcolumns = mastersheet.getLastColumn();
var totalrows = mastersheet.getLastRow();
var masterdata = mastersheet
.getRange(4, 1, totalrows - 3, totalcolumns)
.getDisplayValues();
var data = masterdata.filter(function (item) {
// return item[27] === "CO" && item [29] === "CO";
if (item[27] === "CO" && item[29] === "CO") {
return item[3];
}
});
//tried to map the filtered data into another array below but this is not working as well.
var bsb = data.map(row);
function row(item) {
return item[3];
}
newtab.clearContents();
newtab.getRange(1, 1, data.length, data[0].length).setValues(data);
}
To write a single value from an Array object use Class Range setValue(value) where value could be data[rowIndex][columnIdx].
bsb was declared but not used later.
The code is adding the whole filtered data to the spreadsheet
The Array.prototype.map and it's callback are declared inside the same scope but map is used before the callback declaration. I think that it's better to declare it before calling it, or declare the callback global scope, but this is completely up to you. Think about style, readability and maintainability.
Resource
https://developers.google.com/apps-script/guides/sheets
Related
Declaring variables and functions, in what order?
Create a custom callback in JavaScript
Related
I am currently trying to get App Script to read a ranges of cells from different sheets in a workbook, but I keep getting "Range not found (line 30). I have a bit of experience with Javascript, but App Script is fairly new to me, so some tips and where to find good documentation or tutorials would really help as well.
function getvalues(values2) {
var sheet = SpreadsheetApp.getActiveSheet().getRange(values2)
var raw_values = []
for (var i = 0; i < sheet.length; i++) {
raw_values.push(sheet[i])
}
return raw_values
}
Edit
Here is what the value2 is. They're ranges separated by commas, and each range is from a different sheet in the workbook.
January!W22:W35,February!W22:W35,March!W22:W35,April!W22:W35,May!W22:W35,June!W22:W35,July!W22:W35,August!W22:W35,September!W22:W35,October!W22:W35,November!W22:W35,December!W22:W35
According to the docs, getRange() doesn't seem to specify a comma separated list of ranges as a valid argument. The error is most likely because the function is trying to interpret the entire string as a single range.
You may need to retrieve each range separately and then aggregate them in some way.
I've included an example of how you might do this.
let myRanges = "January!W22:W35,February!W22:W35,March!W22:W35"
function getvalues(values2) {
let raw_values = []
values2.split(",").forEach(a1range => {
let range = SpreadsheetApp.getActiveSheet().getRange(a1range)
console.log(range.getValues())
// get the values from the range and push them to raw_values
})
return raw_values
}
getvalues(myRanges)
Additionally, it looks like getRange() will return a Range object, which doesn't directly contain the values from the range, but has the methods getValue() and getValues() to return the values from the selected cells. getValues appears to return a multidimensional array representing the rows/columns from your range, so you'll need to do some extra processing depending on how you need your data formatted.
https://developers.google.com/apps-script/reference/spreadsheet/range#getValue()
Considering that, it would probably also be a good idea to change the function name to something other than getvalues to avoid confusion.
I'm new to Google Apps Script/Javascript and I'm in the midst of working on the second iteration of my original code in order to make it faster. My first code made a lot of gets and sets to the underlying Google Sheet, while now I'm trying to do everything in arrays before setting the revised values. The current block of code I'm stuck on is my attempt to run an indexOf while running a map function.
The below script is about to run through every row of an array full of timesheet data and make
various amendments to the columns and calculate new column values.
var = amendedTimesheetData = timesheetdata.map(function(item){
item.splice(2, 0, item[0] + " " + item[1]);
item.splice(4, 0, item[0].slice(0, 2) + item[1].slice(0, 2) + date(item[3]));
item.splice(0, 2);
item.splice(3, 0, "Open");
item.splice(4, 0, accountingCode); //SEE ISSUE BELOW
return item
})
}
The accounting code variable relies on having performed a Javascript version of a vlookup based on a value in one of the array columns. The looked-up value exists in another array pulled from a different tab in the sheet. I know the code to perform the lookup looks like this:
var timeTrackingCode = item[6]; //this is the location in the mapped array of the value to be looked up
var timeTrackingCodeRow = codeMappingData.map(function(r){ return r[0]; }).indexOf(timeTrackingCode);
var accountingCode = codeMappingData[timeTrackingCodeRow][1]
The formula in the code above relies on the codeMappingData variable which was made earlier in the parent function before the map function started running.
var codeMappingSheet = ss.getSheetByName('CodeMapping');
var codeMappingRange = codeMappingSheet.getDataRange();
var codeMappingData = codeMappingRange.getValues();
The Question: How do I reference the codeMappingData array while in the map function?
Other options to get around this would be to run a For loop separate from the map, but while trying to learn all the possibilities I'm stuck on trying to understand how I could pass the variable through and achieve all of the column manipulations in as little code as possible. Any guidance on how I could achieve the variable pass through, or tips on how the code could be more efficient would all be very valuable. I've only copied a bit of the code, so it might lack context, but I think the question is clear.
Note that I've assumed that it would be ill-advised to establish the codeMappingData variable within the Map function because then every time it iterates through a row it would be performing a get from the sheet? If I'm incorrect in assuming this, then perhaps the simplest approach is to establish the variable within the map.
Thank you,
The inner or child scope always has access to variables in the outer or parent scope.
function outerFunction(){
const outerScopeVar = 5;
const arr = [1,2,3];
arr.map(num => {
console.info(`I have access to both inner and outer scope variable: ${num} and ${outerScopeVar}`);
})
}
outerFunction();
References:
Scope
You can use the thisArg parameter as described in the Array.prototype.map() documentation. Just add it after your closing curly bracket and refer to it as this within your map function, not codeMappingData.
var timeTrackingCodeRow = codeMappingData.map(function(r) {
Logger.log(this[0]); // logs the first element of codeMappingData
return r[0];
}, codeMappingData).indexOf(timeTrackingCode);
I'm working on an app that scans data into Google sheets. i have two sheets: sheet1 and sheet2. In sheet1 the data is entered into it after a scan is performed. But before the scan data can be entered, i would like to do an if function to check if there is a similar type of data in sheet2 before entering the data. if there isn't one then a message will be displayed such as "There is no such person in the database". I would also like to specify the column to check for similarity in sheet2 e.g A2:A100
Below is the script code
function doGet(e){
var ss = SpreadsheetApp.openByUrl("google sheet url");
//Give your Sheet name here
var sheet = ss.getSheetByName("Sheet1");
return insert(e,sheet);
}
function doPost(e){
var ss = SpreadsheetApp.openByUrl("google sheet url");
//Give your Sheet name here
var sheet = ss.getSheetByName("Sheet1");
return insert(e,sheet);
}
function insert(e,sheet){
// reciving scanned data from client i.e android app
var scannedData = e.parameter.sdata;
var d = new Date();
var ctime= d.toLocaleString();
sheet.appendRow([scannedData,ctime]);
return ContentService
.createTextOutput("Success")
.setMimeType(ContentService.MimeType.JAVASCRIPT);
}
Thanks.
Google Apps Script is in essence JavaScript with different libraries. So your if statements are in essence all the same as there:
if (a1 == a2) {
a1 = 0
}
else if (a1 != a3) {
a1 = 1
}
else {
a1 = 'String'
}
Check on logic operators to find what you can do if you are unfamiliar with that and also read about the switch satement as it's also sometimes useful in place of if statements (not in your case, but might as well read up on it while you are on the topic).
EDIT:
As per your comment, to compare the data from specific columns it's quite simple and there are multiple ways of doing it. Think of it as a structure Spreadsheet → Sheet → Range → Value. So if you already have an object for the sheet you can do range = sheet1.getRange(1,1) which will get you the cell A1. Or you can use A1 notation for the getRange('A1'). If your range is a single cell you can then do range.getValue() which will return the value inside the cell.
Now you want to find if the data exists in another sheet, going 1 by 1 will not be effective as getValue() will bloat the script very quickly. Instead you might want to do vals = sheet2.getDataRange().getValues(). This will return a 2D array of all the values inside of the sheet. If you want to only check a specific column and you know you do not care about the rest you can just replace getDataRange() with something like .getRange(C:C) or the same would be getRange(1, 3, sheet2.getLastRow(), 1).
Then you will simply loop through the 2D array with vals[rowNum][colNum].
If the value is added manually to 1 cell and the script fires with an onEdit trigger, you can also get the value directly from the event object e where it's in onEdit(e).
Read up on getRange() (read those bellow as well) as well as getValue() (and getValues bellow that). Google has excellent documentation, just logically follow the structure for what you want to achieve.
I am writing a script for a Google Docs Spreadsheet to read a list of directors and add them to an array if they do not already appear within it.
However, I cannot seem to get indexOf to return anything other than -1 for elements that are contained within the array.
Can anyone tell me what I am doing wrong? Or point me to an easier way of doing this?
This is my script:
function readRows() {
var column = SpreadsheetApp.getActiveSpreadsheet().getRangeByName("Director");
var values = column.getValues();
var numRows = column.getNumRows();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var directors = new Array();
for (var i = 0; i <= numRows - 1; i++) {
var row = values[i];
if (directors.indexOf(row) == -1) {
directors.push(row);
} else {
directors.splice(directors.indexOf(row), 1, row);
}
}
for (var i = 2; i < directors.length; i++) {
var cell = sheet.getRange("F" + [i]);
cell.setValue(directors[i]);
}
};
When you retrieve values in Google Apps Script with getValues(), you will always be dealing with a 2D Javascript array (indexed by row then column), even if the range in question is one column wide. So in your particular case, and extending +RobG's example, your values array will actually look something like this:
[['fred'], ['sam'], ['sam'], ['fred']]
So you would need to change
var row = values[i];
to
var row = values[i][0];
As an aside, it might be worth noting that you can use a spreadsheet function native to Sheets to achieve this (typed directly into a spreadsheet cell):
=UNIQUE(Director)
This will update dynamically as the contents of the range named Director changes. That being said, there may well be a good reason that you wanted to use Google Apps Script for this.
It sounds like an issue with GAS and not the JS. I have always had trouble with getValues(). Even though the documentation says that it is a two dimensional array, you can't compare with it like you would expect to. Although if you use an indexing statement like values[0][1] you will get a basic data type. The solution (I hope there is a better way) is to force that object into a String() and then split() it back into an array that you can use.
Here is the code that I would use:
var column = SpreadsheetApp.getActiveSpreadsheet().getRangeByName("Director");
var values = column.getValues();
values = String(values).split(",");
var myIndex = values.indexOf(myDirector);
If myDirector is in values you will get a number != -1. However, commas in your data will cause problems. And this will only work with 1D arrays.
In your case: var row = values[i]; row is an object and not the string that you want to compare. Convert all of your values to an array like I have above and your comparison operators should work. (try printing row to the console to see what it says: Logger.log(row))
I ran into a similar problem with a spreadsheet function that took a range as an object. In my case, I was wanting to do a simple search for a fixed set of values (in another array).
The problem is, your "column" variable doesn't contain a column -- it contains a 2D array. Therefore, each value is it's own row (itself an array).
I know I could accomplish the following example using the existing function in the spreadsheet, but this is a decent demo of dealing with the 2D array to search for a value:
function flatten(range) {
var results = [];
var row, column;
for(row = 0; row < range.length; row++) {
for(column = 0; column < range[row].length; column++) {
results.push(range[row][column]);
}
}
return results;
}
function getIndex(range, value) {
return flatten(range).indexOf(value);
}
So, since I wanted to simply search the entire range for the existance of a value, I just flattened it into a single array. If you really are dealing with 2D ranges, then this type of flattening and grabbing the index may not be very useful. In my case, I was looking through a column to find the intersection of two sets.
Because we are working with a 2D array, 2dArray.indexOf("Search Term") must have a whole 1D array as the search term. If we want to search for a single cell value within that array, we must specify which row we want to look in.
This means we use 2dArray[0].indexOf("Search Term") if our search term is not an array. Doing this specifies that we want to look in the first "row" in the array.
If we were looking at a 3x3 cell range and we wanted to search the third row we would use 2dArray[2].indexOf("Search Term")
The script below gets the current row in the spreadsheet and turns it into an array. It then uses the indexOf() method to search that row for "Search Term"
//This function puts the specified row into an array.
//var getRowAsArray = function(theRow)
function getRowAsArray()
{
var ss = SpreadsheetApp.getActiveSpreadsheet(); // Get the current spreadsheet
var theSheet = ss.getActiveSheet(); // Get the current working sheet
var theRow = getCurrentRow(); // Get the row to be exported
var theLastColumn = theSheet.getLastColumn(); //Find the last column in the sheet.
var dataRange = theSheet.getRange(theRow, 1, 1, theLastColumn); //Select the range
var data = dataRange.getValues(); //Put the whole range into an array
Logger.log(data); //Put the data into the log for checking
Logger.log(data[0].indexOf("Search Term")); //2D array so it's necessary to specify which 1D array you want to search in.
//We are only working with one row so we specify the first array value,
//which contains all the data from our row
}
If someone comes across this post you may want to consider using the library below. It looks like it will work for me. I was getting '-1' return even when trying the examples provide (thanks for the suggestions!).
After adding the Array Lib (version 13), and using the find() function, I got the correct row!
This is the project key I used: MOHgh9lncF2UxY-NXF58v3eVJ5jnXUK_T
And the references:
https://sites.google.com/site/scriptsexamples/custom-methods/2d-arrays-library#TOC-Using
https://script.google.com/macros/library/d/MOHgh9lncF2UxY-NXF58v3eVJ5jnXUK_T/13
Hopefully this will help someone else also.
I had a similar issue. getValues() seems to be the issue. All other methods were giving me an indexOf = -1
I used the split method, and performed the indexOf on the new array created. It works!
var col_index = 1;
var indents_column = main_db.getRange(1,col_index,main_db.getLastRow(),1).getValues();
var values = String(indents_column).split(","); // flattening the getValues() result
var indent_row_in_main_db = values.indexOf(indent_to_edit) + 1; // this worked
I ran into the same thing when I was using
let foo = Sheet.getRange(firstRow, dataCol, maxRow).getValues();
as I was expecting foo to be a one dimensional array. On research for the cause of the apparently weird behavior of GAS I found this question and the explanation for the always two dimensional result. But I came up with a more simple solution to that, which works fine for me:
let foo = Sheet.getRange(firstRow, dataCol, maxRow).getValues().flat();
Quick look where I normally bother people tells me here is the new place to ask questions!
I have been making a script to create documentation generated from spreadsheet data which was in turn generated from a Google form. (Hope that makes sense...)
Anyway, I have been very successful with a lot of searching and bit of help but now I want to make my script homogeneous so I don't need to tinker with it when I want to setup new forms etc.
I have the getRowData function going from Googles script tutorials but instead of calling the row data from the normalised Headers i would like these to be generic i.e. Column1, Column2 etc.
I've pasted the tutorial function below, it passes the data to another function which normalizes the headers to use as objects, I was thinking thats where I could make them generic but I'm not sure how to get started on it...
Any help would be greatly appreciated!!
Thanks,
Alex
// getRowsData iterates row by row in the input range and returns an array of objects.
// Each object contains all the data for a given row, indexed by its normalized column name.
// Arguments:
// - sheet: the sheet object that contains the data to be processed
// - range: the exact range of cells where the data is stored
// - columnHeadersRowIndex: specifies the row number where the column names are stored.
// This argument is optional and it defaults to the row immediately above range;
// Returns an Array of objects.
function getRowsData(sheet, range, columnHeadersRowIndex) {
columnHeadersRowIndex = columnHeadersRowIndex || range.getRowIndex() - 1;
var numColumns = range.getEndColumn() - range.getColumn() + 1;
var headersRange = sheet.getRange(columnHeadersRowIndex, range.getColumn(), 1, numColumns);
var headers = headersRange.getValues()[0];
// Browser.msgBox(headers.toSource());
return getObjects(range.getValues(), normalizeHeaders(headers));
// return getObjects(range.getRowIndex);
}
If you want to get the columns using their index, why parse them to object at all? Just use the plain getValues!
var values = sheet.getDataRange().getValues();
var row2 = values[1];
var cell_in_col4 = row2[3];
It looks like you are missing "var" when declaring your columnHeadersRowIndex variable.