I am writing a function in Google Apps Script and it seems the last error I need to get around is a "reference does not exist" error in Google Sheets when I call my function. I don't know what to do about this because it doesn't seem to be a problem with my code.
This is what my code looks like now. It isn't complete because I need to change it for user input, but this is a test.
In a google sheets cell I type in =sortingtesting()
function sortingtesting()
{
var pInfo1 = ['a','b','c','d','e','f','g','h','i','j','k','l','m','o','p','q','r','s']
var pInfo2 = ['a','b','c','d','e','f','g','h','i','j','k','l','m','o','p','q','r','s']
var pInfo3 = ['a','b','c','d','e','f','g','h','i','j','k','l','m','o','p','q','r','s']
var pWO = ['1','','','2','','','3','4','5','6','7','','','8','','','9','10']
var pSearch = ['c', 'b', 'a']
var WO = [];
var Info1 = [];
var Info2 = [];
var Info3 = [];
var Search = [];
for(var i = 0; i < 18; i++)
WO[i] = pWO[i];
for(var i = 0; i < 18; i++)
{
Info1[i] = pInfo1[i];
}
for(var i = 0; i < 18; i++)
{
Info2[i] = pInfo2[i];
}
for(var i = 0; i < 18; i++)
{
Info3[i] = pInfo3[i];
}
for(var i = 0; i < 1; i++)
Search[i] = pSearch[i];
// Declares secondary storage arrays and their counters
var FinalArray1 = [];
var FinalArray2 = [];
var FinalArray3 = [];
var LastArray = [];
var a = 0;
var b = 0;
var c = 0;
var d = 0;
// loop to run and make all of the cells in the work order row relevant to the work order number
for(var row = 0; row < WO.length; row ++)
{
var counter = row - 1;
while(WO[row] == "")
{
WO[row] = WO[counter];
counter--;
}
}
// loop that goes through saving which work orders meet certain search criteria, each search criteria has its own separate secondary array
for(var row = 0; row < Info1.length; row++)
{
if(Info1[row] == Search[0])
{
FinalArray1[a] = WO[row];
a++;
}
}
for(var row = 0; row < Info1.length; row++)
{
if(Info2[row] == Search[1])
{
FinalArray2[b] = WO[row];
b++;
}
}
for(var row = 0; row < Info1.length; row++)
{
if(Info3[row] == Search[2])
{
FinalArray3[c] = WO[row];
c++;
}
}
// loop to run through and get all the work orders that meet all of the criteria
for(var i = 0; i < FinalArray1.length; i++)
{
for(var j = 0; j < FinalArray2.length; j++)
{
for(var k = 0; k < FinalArray3.length; k++)
{
if(FinalArray3[k] == FinalArray2[j] && FinalArray2[j] == FinalArray1[i])
{
LastArray[d] = FinalArray1[i];
d++;
}
}
}
}
return LastArray;
}
Solution Found:
This is my working code with arrays coming in from google sheets as parameters and I just thought it would be nice to put the working prototype out there:
function sortingtesting(WO, Info, Search)
{
// Declares secondary storage arrays and their counters
var FinalArray1 = [];
var FinalArray2 = [];
var FinalArray3 = [];
var LastArray = [];
var a = 0;
var b = 0;
var c = 0;
var d = 0;
// loop to run and make all of the cells in the work order row relevant to the work order number instead of being blank
for(var row = 0; row < WO.length; row ++)
{
var counter = row - 1;
while(WO[row] == "")
{
WO[row] = WO[counter];
counter--;
}
}
// loop that goes through saving which work orders meet certain search criteria, each search criteria has its own separate secondary array to store the work orders that meet the criteria
for(var col = 0; col < Info[0].length; col++)
{
for(var row = 0; row < Info.length; row++)
{
if(Info[row][col] == Search[0])
{
FinalArray1[a] = WO[row];
a++;
}
else if(Info[row][col] == Search[1])
{
FinalArray2[b] = WO[row];
b++;
}
else if(Info[row][col] == Search[2])
{
FinalArray3[c] = WO[row];
c++;
}
}
}
LastArray[0] = 'N/A';
// loop to run through and get all the work orders that meet all of the criteria
for(var i = 0; i < FinalArray1.length; i++)
{
for(var j = 0; j < FinalArray2.length; j++)
{
for(var k = 0; k < FinalArray3.length; k++)
{
if(FinalArray3[k] == FinalArray2[j] && FinalArray2[j] == FinalArray1[i])
{
LastArray[d] = FinalArray1[i];
d++;
}
}
}
}
return LastArray;
}
TL;DR The function should not return an empty array.
By placing return "a valid string"; in various positions in the script (bisecting the code), you will see that return LastArray;` is causing the error.
By running the code in the debugger, LastArray is an empty array.
From experiments, an empty array is not a valid return value for a function called in a formula, neither is an array containing multiple values. An array of containing one integer is valid.
Changing var LastArray = []; to var LastArray = [1]; demonstrates this.
Related
This is my code. I want to make tic toc toe game
I'm stuck at check value in cell. now I want to use for loop to check cell by cell. I don't know how to write it. Pls help me. TT
function onEdit(e) {
var activeSheet = e.source.getActiveSheet();
var cell = activeSheet.getRange("B2:D4");
var data = cell.getValues();
var data1 = data[2][2];
if(cell.getValues !== [["","",""],["","",""],["","",""]]){
Logger.log(cell.getValues());
Logger.log(data1)
for (var i = 2; i < 5; i++)
{
Logger.log("for1");
for (var j = 2; j < 5; j++)
{
Logger.log("for2");
if(data[i][j] == "X")
{
Logger.log("D")
activeSheet.getRange("H2").setValue("Playing");
}else
{
activeSheet.getRange("H2").setValues("");
Logger.log("F")
Logger.log(data1)
}
}
}
}
}
I tried to replicate your code and found some issues.
Array comparison - Comparing array with another array will not work, you have to either compare each element or convert the array into string by using JSON.stringify(array). See example 1 below.
In your for (var i = 2; i < 5; i++) and for (var j = 2; j < 5; j++), this will prompt index out of bounds once it reached 3, 4. The reason for this is the array in var data = cell.getValues() always start at zero. Instead you could use the array size to prevent error. See example 2 below.
setValues can only accept 2 dimensional arrays. See documentation.
if else statement - The else statement will override the value of H2 if the next element in your array is not X.
example 1:
if(JSON.stringify(data) != "[[\"\",\"\",\"\"],[\"\",\"\",\"\"],[\"\",\"\",\"\"]]"){
}
example 2:
for (var i = 0; i < data.length; i++)
{
for (var j = 0; j < data[0].length; j++)
{
//some statement
}
}
This will print every value in tictactoe Range:
Code:
function tictactoe(e) {
var activeSheet = e.source.getActiveSheet();
var cell = activeSheet.getRange("B2:D4");
var data = cell.getValues();
var a1 = ['B', 'C', 'D'];
if(JSON.stringify(data) != "[[\"\",\"\",\"\"],[\"\",\"\",\"\"],[\"\",\"\",\"\"]]"){
for (var i = 0; i < data.length; i++)
{
var column = i+2;
for (var j = 0; j < data[0].length; j++)
{
Logger.log("Value in " + a1[j] + (column) +" : "+ data[i][j]);
}
}
}
}
Output:
Additional info: You can get the A1 notation of the range being populated/edited by using e.range.getA1Notation();
I have two codes, the first of which works and shows all attachments and the second shows only the first. "getAttachmentAndMessage" is used by several of my scripts so I need to have it in a separate function in order not to duplicate my configuration. Please help.
//this function get me all attachments
//this function is just an example of how I built my code below;
function searchEmailsData(){
var search = GmailApp.search('in:inbox newer_than:3d');
var threads = GmailApp.getMessagesForThreads(search);
for (var i = 0 ; i < threads.length; i++) {
var thread = threads[i];
for (var j = 0; j < thread.length; j++) {
var message = thread[j];
var attachments = message.getAttachments();
for (var k = 0; k < attachments.length; k++) {
var attachment = attachments[k];
Logger.log(attachment.getName());
}
}
}
}
//this functions get me only first attachment but I need all of them;
function searchEmailsData2(){
var search = GmailApp.search('in:inbox newer_than:3d');
var threads = GmailApp.getMessagesForThreads(search);
for (var i = 0 ; i < threads.length; i++) {
var thread = threads[i];
var obj = getAttachmentAndMessage(thread);
Logger.log(obj[1].getName())
}
}
function getAttachmentAndMessage(thread){
for (var j = 0; j < thread.length; j++) {
var message = thread[j];
var attachments = message.getAttachments();
for (var k = 0; k < attachments.length; k++) {
var attachment = attachments[k];
var obj = [message, attachment];
return obj;
}
}
}
In order to return not the first attachment but rather all of them, you can modify the getAttachmentAndMessage() function as follows:
function getAttachmentAndMessage(thread) {
var results = [];
for (var j = 0; j < thread.length; j++) {
var message = thread[j];
var attachments = message.getAttachments();
var obj = {'message': message, 'attachments': attachments};
results.push(obj);
}
return results;
}
The above will return a list of objects, in which each objects contains a property message, and a property attachments (which is a list of attachments).
In order to return a list containing only the attachments in the thread, you can use the following variation of the code above:
function getAttachmentAndMessage(thread) {
var results = [];
for (var j = 0; j < thread.length; j++) {
var message = thread[j];
var attachments = message.getAttachments();
results = results.concat(attachments);
}
return results;
}
Your original code
In your original getAttachmentAndMessage() code, you were using a return statement inside of the for loop. That means that upon finding the first message (first iteration of the outer loop) and upon finding its first attachment (first iteration of the inner loop) the function would simply return one value, which is a list containing the first message and its first attachment.
I have been using this script for months, but now i wanted to add it to another sheets or spreadsheet and its not working, on old sheets where it was added moths ago it works fine.
Script is for hiding empty rows and showing rows that has values.
Maybe someone have any idea what to change in script so that it works again?
function hiderows() {
var ss = SpreadsheetApp.openById('1YoInjxlCeBPZP0HlEW0HSYBkdMCGtRaeJFylCQNXnMM');
sheets = ["Lapa1"]
for (var s = 0; s < sheets.length; s++) {
var sh = ss.getSheetByName(sheets[s]);
var firstRow = 1;
var firstCol = 1;
var data = sh.getDataRange().getValues();
var show = null, map = [], index, flag;
for (var i = firstRow - 1, length = data.length; i < length; i++) {
flag = false;
for (var j = firstCol - 1, width = data[0].length; j < width; j++) {
if (data[i][j] ) {
flag = true;
break;
}
}
if (show === flag) {
map[index].rows++;
} else {
index = i + 1;
show = flag;
map[index] = {rows: 1, show: show};
}
}
for (
var i in map) {
if (map[i].show) {
sh.showRows(i, map[i].rows);
} else {
sh.hideRows(i, map[i].rows);
}
}
}
}
I have a google spreadsheet with two columns corresponding to lessons: the first with names of the porfessors (occasionally repeating themselves) and the second with numbers (number of hours). I would like to have as output two columns, the first with the names of the porfessors and the second with the sum of all the hours
I tried with the following code, but it seems to give me back two arrays with the initial colums, as if the condition if (names[names.length-1] == namesColumn[i]) is never met.
What am I doing wrong?
function resumeProfessors() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheets()[1];
var namesColumn = sheet.getRange("C4:C31").getValues();
var lessonsColumn = sheet.getRange("G4:G31").getValues();
var names = [];
names.length = 0;
var lessons = [];
lessons.length = 0;
namesColumn.sort();
for (var i = 0; i < namesColumn.length; i++) {
if (names[names.length-1] == namesColumn[i]){
lessons[lessons.length-1] = lessons[lessons.length-1] + lessonsColumn[i];}
else{
sheet.getRange(i+4, 9).setValue(names[names.length-1] + namesColumn[i]);
names[names.length] = namesColumn[i];
lessons[lessons.length] = lessonsColumn[i];
};}
writeResume(names, lessons);
}
Ty
Given your use-case, I'd recommend using a Pivot table or the =QUERY formula.
However, assuming your input sheet looks something like this -
And the expected output is something like this -
You can try the below code -
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var input = ss.getSheetByName('Sheet1');
var output = ss.getSheetByName('Sheet2');
var inputValues = input.getDataRange().getValues();
Logger.log(inputValues)
for (var i = 1; i < inputValues.length; i++) {
var name = inputValues[i][0];
var totalHours = [];
for (var j = 0; j < inputValues.length; j++) {
var hours = inputValues[j][1];
if (name == inputValues[j][0]) {
totalHours.push(inputValues[j][1]);
}
}
var outputValues = output.getDataRange().getValues();
var newEntry = true;
for (var k = 0; k < outputValues.length; k++) {
if (name == outputValues[k][0]) {
newEntry = false;
}
}
if (newEntry) {
output.appendRow([name,totalHours.reduce(function(a, b) {return a + b})]);
}
}
}
Hope this helps.
At the bottom of each column I'm trying to display how many cells in that column have a particular background colour. This is what I'd like it to do:
Picture of intended result
The problem is it's a Gantt chart and I'll be inserting rows constantly. I can do it with a fixed number of rows using:
=countCellsWithBackgroundColor("#cfe2f3", "B1:B22")
which uses this function I found:
function countCellsWithBackgroundColor(color, rangeSpecification) {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var range = sheet.getRange(rangeSpecification);
var x = 0;
for (var i = 1; i <= range.getNumRows(); i++) {
for (var j = 1; j <= range.getNumColumns(); j++) {
var cell = range.getCell(i, j);
if(cell.getBackgroundColor() == color)
x++;
}
}
return x;
}
I just can't figure out how to give that function every cell in the column. I get errors like "must be a range".
You could use a more efficient way to achieve the same result by using arrays in your function like below
function countCellsWithBackgroundColor(color, rangeSpecification) {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var backGroundColors = sheet.getRange(rangeSpecification).getBackgrounds();
var x = 0;
for (var i = 0; i < backGroundColors.length; i++) {
for (var j = 0; j < backGroundColors[0].length; j++) {
if(backGroundColors[i][j] == color){ x++ }
}
}
return x;
}
WORKING EXAMPLE WITH AUTO UPDATE
function countCellsWithBackgroundColor(color, rangeSpecification) {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var backGroundColors = sheet.getRange(rangeSpecification).getBackgrounds();
var x = 0;
for (var i = 0; i < backGroundColors.length; i++) {
for (var j = 0; j < backGroundColors[0].length; j++) {
if(backGroundColors[i][j] == color){ x++ }
}
}
return x;
}
function onEdit(e)
{
SpreadsheetApp.getActiveSheet().getRange('H26').setValue(Math.random());
}
Now Insert the dummy cell into spreadsheet function for auto update
=countCellsWithBackgroundColor("#0f9d58","A5:A26",H26)