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();
Related
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 use function script in google spread sheet.
this function is used maybe... 150,000 cell.
my question is... infinite loading.
when i use my custom function in sheet, infinite loading appear
how can i resolve that?
here is my script code :
function s2hex(str1){
var s2hex = 0;
var byte_check = 0;
var sheet1 = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("tbl");
var range1 = sheet1.getRange(1, 1, sheet1.getMaxRows(),
sheet1.getMaxColumns());
for(var i = 0; i < str1.length; ++i){
for(var k = 1; k < sheet1.getMaxRows(); ++k){
if(str1[i] == range1.getCell(k, 2).getValue() || str1[i] == " "){
s2hex = s2hex + 1;
byte_check = 1;
break;
}
}
if(byte_check == 0){
s2hex = s2hex + 2;
}
byte_check = 0;
}
return s2hex;
};
getMaxRows() will return all rows in the sheet. If you have content in A1:A10 and there are empty rows from A10 to A100000. It'll return 100,000 instead of 10. Use getLastRow() instead.
If you're only using the second column,Specify 2 as column number and 1 instead of getMaxColumns()
Use getValues() to get a single array instead of making multiple calls to spreadsheet.
Try Modifying from:
var range1 = sheet1.getRange(1, 1, sheet1.getMaxRows(), sheet1.getMaxColumns());
for(var i = 0; i < str1.length; ++i)
{
for(var k = 1; k < sheet1.getMaxRows(); ++k)
{
if(str1[i] == range1.getCell(k, 2).getValue() || str1[i] == " ")
{
s2hex = s2hex + 1;
To
var lr =sheet1.getLastRow(); //One last row call to be used in multiple places
var range = sheet1.getRange(1, 2, lr,1);
var range1 = range.getValues(); //get all values in a single call
for(var i = 0; i < str1.length; ++i)
{
for(var k = 0; k < lr; ++k) //k to 0.Array index start at 0
{
if(str1[i] == range1[k][0] || str1[i] == " ") //Check the 2D value of already retrieved array
{
s2hex = s2hex + 1;
150,000 function calls will slow down any sheet.
That said, your function is in dire need of optimization: you need to remove the need for extensive calls over the Spreadsheet interface. Each such call can take as much as 0.1 second. You can do this simply, by reading the entire range of your values to search with getValues(), and then iterating that in the same manner as you do currently.
I have a tremendous volume of sparse matrix data, where i want to programmatically set as active cell wherever there is (0) when I run the below snippet (function).
Which would ease me for manipulation of code labels
The data is purely random. The process has to begin from the current active cell and it has to loop again from beginning if end of the data is found.
I get unexpected results,
function getzero() {
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getDataRange();
var Values = range.getValues();
for (var i = 0; i < Values.length; i++) {
for (var j = 0; j < Values[i].length; j++) {
if (Values[i][j] == 0) {
sheet.setActiveSelection(sheet.getRange(i + 1, j + 1));
break;
}
}
}
}
This should take care of finding all the zeros. Also added a menu for you to run the command from the sheets directly. Just paste code in the script editor and reload the sheet.
// This function addes a menu Zero and submenu getzero to access your function directly from spreadsheet
function onOpen(){
var ss = SpreadsheetApp.getActive()
var menu = [{name:"Find Zero",functionName: "getzero"}]
ss.addMenu("Zeroes", menu)
}
function getzero() {
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getDataRange();
var Values = range.getValues();
var selectedRange = sheet.getActiveRange()
// find active cells row and column
var startRow = selectedRange.getRow() -1
var startCol = selectedRange.getColumn()
var notFoundZero = true
//Use the active cells row and column to start the loop
for (var i = startRow; i < Values.length; i++) {
if (i == startRow){
var j = startCol
}
else {
j =0
}
for (j ; j < Values[i].length; j++) {
// Using "===" makes sure the type is also match else blank is considered as zero too.
if (Values[i][j] === 0) {
Logger.log("Values Row X Col:" + i + " X " + j)
//The below line works as well as sheet.setActiveSelection
sheet.getRange(i + 1, j + 1).activate()
//Below code escapes the outer loop
i = Values.length;
// this boolean is used to runs or stops the next loop
notFoundZero = false;
// breaks inner loop
break;
}
}
}
if(notFoundZero){
for (var i = 0; i <= startRow; i++) {
if (i == startRow){
var runTill = startCol
}
else {
runTill = Values[i].length
}
for (var j=0 ; j < runTill; j++) {
if (Values[i][j] === 0) {
sheet.getRange(i + 1, j + 1).activate()
// same as above
i = Values.length;
//Used to alert if no more zeros found
notFoundZero = false;
break;
}
}
}
}
if(notFoundZero)
{
var ui = SpreadsheetApp.getUi()
ui.alert("No More zero Found")
}
}
This will check cell values after selection if no zeroes are found it will check above the selection. But after that it will stop at the selection and alert no zeroes found, this prevents a runaway loop.
Give it a go and let me know how it goes.
Edit: Below Code for searching in reverse
Below Code Has not been Tested, Might have errors. This was not the primary Question, hence did not check for errors.
function getzeroRev() {
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getDataRange();
var Values = range.getValues();
var selectedRange = sheet.getActiveRange()
// find active cells row and column
var startRow = selectedRange.getRow() -1
var startCol = selectedRange.getColumn()
var notFoundZero = true
//Use the active cells row and column to start the loop
for (var i = startRow; i >=0; i--) {
if (i == startRow){
var j = startCol
}
else {
j =values[i].length
}
for (j ; j >=0; j--) {
// Using "===" makes sure the type is also match else blank is considered as zero too.
if (Values[i][j] === 0) {
Logger.log("Values Row X Col:" + i + " X " + j)
//The below line works as well as sheet.setActiveSelection
sheet.getRange(i + 1, j + 1).activate()
//Below code escapes the outer loop
i = Values.length;
// this boolean is used to runs or stops the next loop
notFoundZero = false;
// breaks inner loop
break;
}
}
}
if(notFoundZero){
for (var i = values.length; i >= startRow; i--) {
if (i == startRow){
var runTill = startCol
}
else {
runTill = 0
}
for (var j=0 ; j >= runTill; j--) {
if (Values[i][j] === 0) {
sheet.getRange(i + 1, j + 1).activate()
// same as above
i = Values.length;
//Used to alert if no more zeros found
notFoundZero = false;
break;
}
}
}
}
if(notFoundZero)
{
var ui = SpreadsheetApp.getUi()
ui.alert("No More zero Found")
}
}
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.
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)