In google app scripts, I am pulling in data (all strings) from a google sheet and then trying to split up the array into separate columns excluding the first row (header row). The data has an arbitrary amount of rows and columns. "projects" are the headers and "tasks" is the data underneath each header.
// load in task names
function LoadTasks() {
var sheet = ss.getSheetByName("Task List");
sheet.activate();
var allTasks = sheet.getDataRange().getValues();
return allTasks;
}
function Analysis(project, tasks) {
var sheet = ss.getSheetByName(project);
sheet.activate;
var taskLength = tasks.length;
var sheetLength = (taskLength*4) + 10;
Logger.log("The Project is " + project + " with task length of " + taskLength + " and task data of: " + tasks);
}
taskData = LoadTasks();
numProjects = taskData[0].length;
// load all project names into single array
for (i = 0; i < numProjects; i++) {
projectNames[i] = taskData[0][i];
}
for (i = 0; i < numProjects; i++) {
project = projectNames[i];
j = 1;
while (taskData[j][i] != null) {
tasks[j-1] = taskData[j][i];
j++;
}
Analysis(project, tasks)
tasks = [];
}
In the very last while loop, how can I check to see if the array value I'm looking at holds a value (does not contain a null, undefined, or blank). My current method gives me the error: Cannot read property "0.0" from undefined. (line 91, file "Code")
***Line 91: while (taskData[j][i] != null) {
The method getValues() returns a double array of values which can be numbers, strings, or Date objects. There are no "null" or "undefined" among the values. Blank cells are represented as empty strings "" and you can detect them by comparison to "".
That said, one should control the index bounds explicitly:
while (j < taskData.length && taskData[j][i] !== "") {
tasks[j-1] = taskData[j][i];
j++;
}
Related
I'm trying to make a google script to automate some calculations in a google spreadsheet.
I have several things in this sheet and what i need is, for all the date values i have on A column (between rows 3 and 20) i need to search on the column T (between rows 17 and 50) and when a date match i need to grab the value in the cell next to the data in column T.
Then with this sum i need to go to the B move one row down and substract that sum from the above cell.
example:
A
17/8/2017
18/8/2017
19/8/2017
B
100
empty
empty
T
empty
empty
18/8/2017
18/8/2017
18/8/2017
19/8/2017
U
empty
empty
5
5
2
1
After running the script B should be:
100
88
87
My code:
function burnDown(){
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var data = sheet.getDataRange().getValues();
var completed = sheet.getRange(20, 20, 40);
for(var i = 3; i<20;i++){
for(var j = 1; i<50; j++){
var sum = 0;
if(data[i][0] == completed[j][0])
{
sum = sum + completed[j][20];
Logger.log(completed[j][21] + "" + sum);
}
j++;
}
i++;
}
}
I'm stuck at this point where I have to make the match but i'm getting the error: TypeError: Cannot read property "0" from undefined. (line 9, file "BurnDown"
Thanks,
No values in completed. Perhaps you need a getValues() somewhere
function burnDown()
{
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var data = sheet.getDataRange().getValues();
//var completed = sheet.getRange(20, 20, 40);//this is a range
var completed = sheet.getRange(20,20,40).getValues();//this creates a 2d array of values
for(var i = 3;i<20;i++)
{
for(var j = 1; j<50; j++)
{
var sum = 0;
if(data[i][0]==completed[j][0])
{
sum=sum + completed[j][20];
Logger.log(completed[j][21] + "" + sum);
}
//j++;//not sure why these are here the loop increments
}
//i++;//same question as above
}
}
This snippet of code is throwing me for a loop.
if(colList[i] != checkList[i]) {
var colTest = colList[i];
var checkTest = checkList[i];
As you can see from this screenshot from the debug the values are identical.
ScreenShot
Any hints as to why the if statement thinks these values are different?
EDIT: Here is a screenshot showing the full arrays.
Again, I'm not sure why this matters. In fact for testing purposes I have both arrays pulling from the exact same source data.
2nd Edit:
Here is all the relevant code. Again, as you can see the arrays are identical.
var colList = sheet.getRange(startRow,watchCol,lastRow,1).getValues(); // Data set with all values to watch
var checkList = sheet.getRange(startRow,watchCol,lastRow,1).getValues(); // Data set with all the check values
function timeStamp() {
for(var i = 0; i <= colList.length; i++)
if(colList[i] != checkList[i]) {
return colList
return checkList
Here is the full code that is trying to treat it as a multidimensional array. This code does not work and returns "Cannot read property "0" from undefined. (line 13,"
var sheet = SpreadsheetApp.getActive().getSheetByName("Sheet1");
var startRow = 2; // First row with Data
var lastRow = sheet.getLastRow() - startRow;
var watchCol = 2; // Column to check for changes
var checkCol = 7; // Column to check against
var timeCol = 3; // Column to put the time stamp in
var colList = sheet.getRange(startRow,watchCol,lastRow,1).getValues(); // Data set with all values to watch
var checkList = sheet.getRange(startRow,watchCol,lastRow,1).getValues(); // Data set with all the check values
function timeStamp() {
for(var i = 0; i <= colList.length; i++)
for(var j = 0; j < checkList.length; j++){
if(colList[i][j] != checkList[i][j]) {
return colList
return checkList
sheet.getRange(i + startRow,checkCol).setValue(colList[i]);
sheet.getRange(i + startRow,timeCol,1,1).setValue(new Date());
}
}
}
According to your screenshots it's simple.
Your's arrays doesn't contain strings, they contain array that contain string, and thus to compare is true, because two arrays will always be different, that because arrays in js are objects and when you try to compare objects it compares that references of them , not the value.
So you should make array of strings, or just to add [0] to each side in the if
As I said in the comment, you have that one index arrays inside another array, so yours are multi-dimensional arrays and you have to use 2 indexes to access its values, i = row and j = column
var checkList = [["Beef"], ["Red"], ["Career"], ["Chicken"], ["Red"], ["Kids"], ["Beef"], ["Red"]];
var colList = [["Beef"], ["Red"], ["Career"], ["Chicken"], ["Red"], ["Kids"], ["Beef"], ["Red"]];
function timeStamp() {
for(var i = 0; i < colList.length; i++){
for(var j = 0; j < checkList[i].length; j++){
if(colList[i][j] != checkList[i][j]) {
console.log('not equal');
} else{
console.log('equal');
}
}
}
}
timeStamp();
As it turns out adding .String() at the end of the function creating the arrays fixed the issue and allowed them to compare correctly.
I'm new to google script and I want to compare two rows from two sheets & if any cell data value matches it'll increase a value. But I'm getting an error like this,
TypeError: Cannot read property "0" from undefined.
Here are my codes,
function onFormSubmit(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Form Responses 1");
var ans_sheet = ss.getSheetByName('Correct_Answers');
var getValues = sheet.getRange(2, 1, sheet.getLastRow(), sheet.getLastColumn()).getValues();
var realValues = ans_sheet.getRange(2, 1, ans_sheet.getLastRow(), ans_sheet.getLastColumn()).getValues();
for (var i in getValues) {
for (var j in getValues[i]) {
var matched = 0;
if (getValues[i][j] == realValues[i][j]) {
matched++;
} else {
getValues.push(1);
}
}
}
I know I'm doing something wrong here but since I'm new I can't figure it out. Need this help badly from experts. Thanks.
This could be because the two ranges have different lengths or because you are appending 1's to the first array in which case there is also nothing to subscript.
At some point you are trying to access the first column (index 0) of a row that is not in the other range or the first column of 1 which does not exist.
since you are iterating through getValues you can do a check at the beginning of the loop.
for (var i in getValues) {
if(i == realValues.length){break;}
for (var j in getValues[i]) {
if(j == realValues[j].length){break;}
var matched = 0;
if (getValues[i][j] == realValues[i][j]) {
matched++;
}
}
}
Good morning.
Very new coder with little background. I need to merge data from a google spreadsheet into an email, without using an add-on. I borrowed this code from a site and I'm able to produce an email but it will only pull from the first email address column. I need to send an email to both the manager and director. Their email address will be stored in two separate columns with unique labels. I can't change the spreadsheet data as the spreadsheet is storing responses pulled from a survey form that is already in progress (example column layout below):
Name / Email Address / Director Name / Director Email Address / Response 1 / Response 2 / etc...
Everything I've researched will send an email from one column, but not two, or a "cc".
Below is the borrowed code. Would very much appreciate any help on how to modify the code to send the "response" data to both the Manager and Director in one email.
kind regards,
KA
function sendEmails() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var dataSheet = ss.getSheets()[0];
var dataRange = dataSheet.getRange(2, 1, dataSheet.getMaxRows() - 1, 15);
var templateSheet = ss.getSheets()[1];
var emailTemplate = templateSheet.getRange("A1").getValue();
// Create one JavaScript object per row of data.
var objects = getRowsData(dataSheet, dataRange);
// For every row object, create a personalized email from a template and send
// it to the appropriate person.
for (var i = 0; i < objects.length; ++i) {
// Get a row object
var rowData = objects[i];
// Generate a personalized email.
// Given a template string, replace markers (for instance ${"First Name"}) with
// the corresponding value in a row object (for instance rowData.firstName).
var emailText = fillInTemplateFromObject(emailTemplate, rowData);
var emailSubject = "Data Survey";
MailApp.sendEmail(rowData.emailAddress, emailSubject, emailText);
}
}
// Replaces markers in a template string with values define in a JavaScript data object.
// Arguments:
// - template: string containing markers, for instance ${"Column name"}
// - data: JavaScript object with values to that will replace markers. For instance
// data.columnName will replace marker ${"Column name"}
// Returns a string without markers. If no data is found to replace a marker, it is
// simply removed.
function fillInTemplateFromObject(template, data) {
var email = template;
// Search for all the variables to be replaced, for instance ${"Column name"}
var templateVars = template.match(/\$\{\"[^\"]+\"\}/g);
// Replace variables from the template with the actual values from the data object.
// If no value is available, replace with the empty string.
for (var i = 0; i < templateVars.length; ++i) {
// normalizeHeader ignores ${"} so we can call it directly here.
var variableData = data[normalizeHeader(templateVars[i])];
email = email.replace(templateVars[i], variableData || "");
}
return email;
}
//////////////////////////////////////////////////////////////////////////////////////////
//
// The code below is reused from the 'Reading Spreadsheet data using JavaScript Objects'
// tutorial.
//
//////////////////////////////////////////////////////////////////////////////////////////
// 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];
return getObjects(range.getValues(), normalizeHeaders(headers));
}
// For every row of data in data, generates an object that contains the data. Names of
// object fields are defined in keys.
// Arguments:
// - data: JavaScript 2d array
// - keys: Array of Strings that define the property names for the objects to create
function getObjects(data, keys) {
var objects = [];
for (var i = 0; i < data.length; ++i) {
var object = {};
var hasData = false;
for (var j = 0; j < data[i].length; ++j) {
var cellData = data[i][j];
if (isCellEmpty(cellData)) {
continue;
}
object[keys[j]] = cellData;
hasData = true;
}
if (hasData) {
objects.push(object);
}
}
return objects;
}
// Returns an Array of normalized Strings.
// Arguments:
// - headers: Array of Strings to normalize
function normalizeHeaders(headers) {
var keys = [];
for (var i = 0; i < headers.length; ++i) {
var key = normalizeHeader(headers[i]);
if (key.length > 0) {
keys.push(key);
}
}
return keys;
}
// Normalizes a string, by removing all alphanumeric characters and using mixed case
// to separate words. The output will always start with a lower case letter.
// This function is designed to produce JavaScript object property names.
// Arguments:
// - header: string to normalize
// Examples:
// "First Name" -> "firstName"
// "Market Cap (millions) -> "marketCapMillions
// "1 number at the beginning is ignored" -> "numberAtTheBeginningIsIgnored"
function normalizeHeader(header) {
var key = "";
var upperCase = false;
for (var i = 0; i < header.length; ++i) {
var letter = header[i];
if (letter == " " && key.length > 0) {
upperCase = true;
continue;
}
if (!isAlnum(letter)) {
continue;
}
if (key.length == 0 && isDigit(letter)) {
continue; // first character must be a letter
}
if (upperCase) {
upperCase = false;
key += letter.toUpperCase();
} else {
key += letter.toLowerCase();
}
}
return key;
}
// Returns true if the cell where cellData was read from is empty.
// Arguments:
// - cellData: string
function isCellEmpty(cellData) {
return typeof(cellData) == "string" && cellData == "";
}
// Returns true if the character char is alphabetical, false otherwise.
function isAlnum(char) {
return char >= 'A' && char <= 'Z' ||
char >= 'a' && char <= 'z' ||
isDigit(char);
}
// Returns true if the character char is a digit, false otherwise.
function isDigit(char) {
return char >= '0' && char <= '9';
}
According to the documentation : Mail App, the recipent represents the addresses of the recipients, separated by commas
So in order to send to several recipents, just make a string separated by comma:mike#example.com, mike2#example.com
You could pick the rows you want and do something like this:
MailApp.sendEmail(rowData[0].emailAddress + ',' + rowData[1].emailAdress, emailSubject, emailText);
I am trying to list three values from each row that contains a specific value in the "Status" column within coded HTML inside a Google Script function. When I run the sendDailyDigest function below, it times out. I am assuming a have some type of error in the for loop inside the html message variable, but I can't seem to figure it out.
I am relatively new to scripting and would be grateful for someone pointing me in the right direction.
Thank you!
function sendDailyDigest() {
var ss = SpreadsheetApp.openById(PRIMARY_SPREADSHEET_ID);
var sheet = ss.getSheets()[0];
var lastRow = sheet.getLastRow();
var data = getRowsData(sheet);
// Count how many requests are awaiting approval
var numSubmitted = 0;
for (var i = 2; i < lastRow; i++) {
if (sheet.getRange(i, getColIndexByName("Status")).getValue() == "SUBMITTED") {
numSubmitted++;
}
}
var message = "<HTML><BODY>"
+ "<P>" + "The following requests await your approval."
+ "<P>" + "\xa0"
+ "<P>" + "<table><tr><td><b>Request ID</b></td><td><b>Requested By</b></td><td><b>Start Date</b></td></tr>"
// List each request pending approval
for (var j = 0; j < data.length; ++j) {
var row = data[j];
row.rowNumber = j + 2;
if (row.status == "SUBMITTED") {
"<tr><td>" + row.rowNumber + "</td><td>" + row.username + "</td><td>" + row.firstDay + "</td></tr>"
}
}
+ "</table>"
+ "</HTML></BODY>";
GmailApp.sendEmail('username#domain.com', numSubmitted + 'Leave Requests Awaiting Approval', '', {htmlBody: message});
}
function getColIndexByName(colName) {
var ss = SpreadsheetApp.openById(PRIMARY_SPREADSHEET_ID);
var sheet = ss.getSheets()[0];
var numColumns = sheet.getLastColumn();
var row = sheet.getRange(1, 1, 1, numColumns).getValues();
for (i in row[0]) {
var name = row[0][i];
if (name == colName) {
return parseInt(i) + 1;
}
}
return -1;
}
/////////////////////////////////////////////////////////////////////////////////
// Code reused from Reading Spreadsheet Data using JavaScript Objects tutorial //
/////////////////////////////////////////////////////////////////////////////////
// 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
// This argument is optional and it defaults to all the cells except those in the first row
// or all the cells below columnHeadersRowIndex (if defined).
// - 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) {
// etc.
The most obvious error I see right now is that you are using the .getLastRow() method, which always times out, even if your code is functional. Instead, try using .getMaxRows(). I used to use the .getLastRow() method at first, but then realized that it doesn't work for some reason. When I used the .getMaxRows() method, the for loop did not time out on me.