Script keeps looping Google Script - google-apps-script

I have a simple script that I would like to check if there is data in column J, but it seems to keep looping and bringing up the notification...
function checkGaps() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Subgrade'); //source sheet
var testrange = sheet.getRange('J18:J'); //range to check
var testvalue = (testrange.getValues());
//Condition check in J18:J
for (i=0; i<testvalue.length;i++) {
if ( testvalue[i] != "") {
Browser.msgBox('Possible duplicates found marked in RED.');
}
}
}
Anyone can help?

If you want to stop the loop after the first time it finds a value you can add a break inside the if:
for (i=0; i<testvalue.length;i++) {
if ( testvalue[i] != "") {
Browser.msgBox('Possible duplicates found marked in RED.');
break;
}
}

Related

Problem with the order of IF statements Google Script

I am struggling to set up my IF statements properly, basically I want to check if there is data in column J, if empty continue on with the rest of the script, if there is data ask if the user wants to continue, and then either continue or terminate depending on their response.
Here is the start of my code:
function exportSg() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Subgrade'); //source sheet
var testrange = sheet.getRange('J18:J'); //range to check
var testvalue = (testrange.getValues());
var ui = SpreadsheetApp.getUi();
for (i=0; i<testvalue.length;i++) {
if ( testvalue[i] != "") {
var response = ui.alert("Gaps greater than 50 meters identified in report, unhide column J to identify. \n\n Do you want to cancel the export?",ui.ButtonSet.YES_NO);
Logger.log(response);
if (response == ui.Button.YES) {
SpreadsheetApp.getActive().toast("Export Cancelled...")}else {
SpreadsheetApp.getActive().toast("Exporting Subgrade Report...");
The problem seems to be when there is no data in column J it does not continue with the export.
Anyone fancy shedding some light??
As mentioned in a previous answer to you, you can use a break to stop a loop.
In your case, that means to have a break when the user clicks the YES button:
if (response == ui.Button.YES) {
SpreadsheetApp.getActive().toast("Export Cancelled...");
break;
}
If you want to export even if there is no data to be used by the for statement, you need to move the export part to outside of it:
function exportSg() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Subgrade'); //source sheet
var testrange = sheet.getRange('J18:J'); //range to check
var testvalue = (testrange.getValues());
var ui = SpreadsheetApp.getUi();
var shouldExport = true;
for (i=0; i<testvalue.length;i++) {
if ( testvalue[i] != "") {
var response = ui.alert("Gaps greater than 50 meters identified in report, unhide column J to identify. \n\n Do you want to cancel the export?",ui.ButtonSet.YES_NO);
Logger.log(response);
if (response == ui.Button.YES) {
SpreadsheetApp.getActive().toast("Export Cancelled...");
shouldExport = false;
break;
}
}
}
if (shouldExport) {
myExportFunction();
}
}

Formula to edit a cell based on if-else condition

I am planning to create a cell where if the cells in G2 is empty then it will copy the content from row A but if the cell in column G is updated with something else I dont want to copy the content from row A. I have done code something like this in App Script but it is not working.
function getLast(range) {
var getResult = function(range) {
if (!((range.getNumRows() > 1 && range.getNumColumns() == 1) || (range.getNumRows() == 1 && range.getNumColumns() > 1))) {
throw new Error("Please input one row or one column.");
}
var v = Array.prototype.concat.apply([], range.getValues());
var f = Array.prototype.concat.apply([], range.getFormulas());
var i;
for (i = v.length - 1; i >= 0; i--) {
if (v[i] != "" || f[i] != "") break;
}
return i + 1;
};
if (Array.isArray(range)) {
return range.map(function(e) {
return getResult(e);
});
} else {
try {
range.getA1Notation();
} catch (e) {
throw new Error("Inputted value is not a range.");
}
return getResult(range);
}
}
function formulasheets(){
var ss = SpreadsheetApp.openByUrl(url);
var sheet = ss.getSheetByName("USERNAMES");
sheet.getRange("G2").setFormula('=IF(G2="",A2, ');
var range1 = sheet.getRange("B:B");
var fillDownRangecolumnI = sheet.getRange(2, 7, lr-1);
sheet.getRange("G2").copyTo(fillDownRangecolumnA);
}
I have attached the image of my google sheet to explain better on what I want. Can ayone help me on this. Thank you.
If Cell G2 & G5 is empty
It should be updated with content in row A
I see your question, and I would like to give a try, hope it is useful for you. Please refer to my edited code below, I think it is better for you to use for loop to check for blank, and to avoid write formula:
function formulasheets(){
var ss = SpreadsheetApp.openByUrl(url);
var sheet = ss.getSheetByName("USERNAMES");
for (var x=1;x <= sheet.getLastRow();x++){
var cell = sheet.getRange(x,7);
if (cell.isBlank()){
sheet.getRange(x,1).copyTo(cell);
}
}
}
Before execution
After execution, accept if help, it is my first time answer G Apps Script :)

Split a Google Sheet into multiple tabs based on column values

I used a previous answer (thanks kessy!) to split 7000 or so rows into 40 or so different tabs based upon values in a column. I ran the same script on another nearly identical file and I get the error "TypeError: Cannot read property 'getRange' of null (line 5, file "Code")". I tried with a greatly simplified file and get the same error. Any help getting this to work is very much appreciated.
function myFunction() {
var sheet = SpreadsheetApp.getActiveSheet();
// This var will contain all the values from column C -> Room
var columnRoom = sheet.getRange("C:C").getValues();
// This var will contain all the rows
var rows = SpreadsheetApp.getActiveSheet().getDataRange().getValues();
//Set the first row as the header
var header = rows[0];
//Store the rooms already created
var completedRooms = []
//The last created room
var last = columnRoom[1][0]
for (var i = 1; i < columnRoom.length; i++) {
//Check if the room is already done, if not go in and create the sheet
if(!completedRooms.includes(columnRoom[i][0])) {
//Set the Sheet name = room (except if there is no name, then = No Room)
if (columnRoom[i][0] === "") {
var currentSheet = SpreadsheetApp.getActiveSpreadsheet().insertSheet("No Room");
} else {
var currentSheet = SpreadsheetApp.getActiveSpreadsheet().insertSheet(columnRoom[i][0]);
}
//append the header
currentSheet.appendRow(header);
currentSheet.appendRow(rows[i]);
completedRooms.push(columnRoom[i][0])
last = columnRoom[i][0]
} else if (last == columnRoom[i][0]) {
// If the room's sheet is created append the row to the sheet
var currentSheet = SpreadsheetApp.getActiveSpreadsheet()
currentSheet.appendRow(rows[i]);
}
}
}
I am not sure what is exactly your goal, but based on the error message you are getting it seems that you are not getting the active sheet properly. Instead, I would suggest you to specify the sheet by its name. Let's assume the desired name of the sheet you want to get is Sheet1. Then, in the first line of your function you can replace this:
var sheet = SpreadsheetApp.getActiveSheet();
with this:
var sheet = SpreadsheetApp.getActive().getSheetByName('Sheet1');
I also optimized your code a little by removing all the unnecessary SpreadsheetApp.getActiveSpreadsheet() calls:
function myFunction() {
var ss = SpreadsheetApp.openById("SpreadsheetId");
var sheet = ss.getSheetByName('Sheet1');
// This var will contain all the values from column C -> Room
var columnRoom = sheet.getRange("C:C"+sheet.getLastRow()).getValues();
// This var will contain all the rows
var rows = sheet.getDataRange().getValues();
//Set the first row as the header
var header = rows[0];
//Store the rooms already created
var completedRooms = []
//The last created room
var last = columnRoom[1][0]
for (var i = 1; i < columnRoom.length; i++) {
//Check if the room is already done, if not go in and create the sheet
if(!completedRooms.includes(columnRoom[i][0])) {
//Set the Sheet name = room (except if there is no name, then = No Room)
if (columnRoom[i][0] === "") {
var currentSheet = ss.insertSheet("No Room");
} else {
var currentSheet = ss.insertSheet(columnRoom[i][0]);
}
//append the header
currentSheet.appendRow(header);
currentSheet.appendRow(rows[i]);
completedRooms.push(columnRoom[i][0])
last = columnRoom[i][0]
} else if (last == columnRoom[i][0]) {
// If the room's sheet is created append the row to the sheet
sheet.appendRow(rows[i]);
}
}
}
You can also run a loop within the loop and keep things server side for a faster result (at least it worked for me, I was having trouble with long spreadsheets timing out).
You have to know how many columns you want to pass over, maybe there is a better way to push the values than I have done (I only dabble in script).
function splitSheets() {
var theWorkbook = SpreadsheetApp.getActiveSpreadsheet();
var theSheet = theWorkbook.getSheetByName("Master");
//Let's delete any sheets that were previously split, so we can rerun the script again and again
var sheets = theWorkbook.getSheets();
for (i = 0; i < sheets.length; i++) {
switch(sheets[i].getSheetName()) {
case "Master":
break;
default:
theWorkbook.deleteSheet(sheets[i]);
}
}
// This var will contain all the values from column C -> Your splitting Key
var key = theSheet.getRange("C:C").getValues();
// This var will contain all the rows
var rows = theSheet.getDataRange().getValues();
//Set the first row as the header, get the range so we can keep the formatting
var headerFormat = theSheet.getRange("2:2");
//Store the rooms already created
var completedSheets = [];
//We start at i=2 because we're on row 3, row zero for the button, row one for the header
for (var i = 2; i < key.length; i++) {
//We don't want to run the loop if we've already created the blank page and the row key is also blank.
if(completedSheets.includes('Blank') && key[i][0] === ""){
//do nothing
}else{
//Check if the room is already done, if not go in and create the sheet
if(!completedSheets.includes(key[i][0]) ) {
//Set the Sheet name = unique key (except if there is no name, then = Blank)
if (key[i][0] === "") {
var currentSheet = theWorkbook.insertSheet("Blank");
} else {
var currentSheet = theWorkbook.insertSheet(key[i][0]);
}
//To avoid pasting formulas, we have to paste contents, copying allows us to keep formatting
headerFormat.copyTo(currentSheet.getRange(1,1),{contentsOnly:true});
headerFormat.copyTo(currentSheet.getRange(1,1),{formatOnly:true});
//Now here find all the rows containing the same key address and push them, this way doing it server side
var theNewRows =[];
var b=0;
for(var j = 1; j < rows.length; j++) {
if((rows[j][2] == key[i][0]) || (rows[j][2] === '' && currentSheet.getName() == "Blank")){
theNewRows[b]=[];//Initial new array
theNewRows[b].push(rows[j][0],rows[j][1],rows[j][2],rows[j][3],rows[j][4],rows[j][5],rows[j][6],rows[j][7],rows[j][8]);
b++;
}
}
var outrng = currentSheet.getRange(2,1,theNewRows.length,9);//Make the output range the same size as the output array
outrng.setValues(theNewRows);
//The new sheet name gets added to the completed sheets list and the value of var last is updated in prep of next step
if(currentSheet.getSheetName() == 'Blank') {
completedSheets.push('Blank');
last = "Blank";
}else{
completedSheets.push(key[i][0])
last = key[i][0]
}
}
}
}
//And return to the Master
SpreadsheetApp.setActiveSheet(theWorkbook.getSheetByName('Master'));
}
Example here, just click the button on the page
https://docs.google.com/spreadsheets/d/1pfeU2CFDbZbA4O0b4z80l5MyCKDNQnUdkpKlzODbAiI/edit?usp=sharing
It's not perfect, but hope it helps.

Script for Google sheet to change a cells value based on another cells value (in a column range)

Here is the scenario:
Column E in my Googlesheet has a dropdown list of Yes and No. Everytime the user answers No, I want the corresponding cell in Column G to have the words "Not Applicable". But if user answers Yes, I want that cell in G to have another dropdown list of Yes and No.
I tried to build on the script that I got from this thread:
Google Sheets formula to change a cells value based on another cells value
It's almost perfect, but I can't make it to work for a range (an entire column, preferrably). Any advice would be appreciated :)
Since I wasn't getting any success trying to tweak it for my own use, here's a copy code from the mentioned thread:
function onEdit(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet()
var s=ss.getActiveSheet()
var nota=e.range.getA1Notation()
if(nota=="E10"){
var val=e.range.getValue()
if(val=="Levy"){
s.getRange("E11").setDataValidation(null)
s.getRange("E11").setValue("Monthly")
}
else{
s.getRange('E11').clearContent()
var cell = s.getRange('E11');
var rule = SpreadsheetApp.newDataValidation().requireValueInList(['Triannual', 'Quarterly']).build();
cell.setDataValidation(rule);
}}}
And here's my dumb attempt:
function onEdit(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet()
var s=ss.getActiveSheet()
var nota=e.range.getA1Notation()
if(nota=="E2:E500"){
var val=e.range.getValue()
if(val=="No"){
s.getRange("G2:G500").setDataValidation(null)
s.getRange("G2:G500").setValue("Not Applicable")
}
else{
s.getRange('G2:G500').clearContent()
var cell = s.getRange('G2:G500');
var rule = SpreadsheetApp.newDataValidation().requireValueInList(['Yes','No']).build();
cell.setDataValidation(rule);
}}}
You need to loop a range of cells, which is why the original script isn't working. .getValue() returns a single cell, you can't have a range (as in your edited script).
This script will look at the entire page and loop it each time. This is preferable because you don't have to keep data in order. In other words, you can jump around Column E and mark things "Yes" or "No" as they come up. Blank cells are ignored.
function addValidation() {
// Get the spreadsheet and active sheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
// Get the data as an Array you can iterate and manipulate
var data = sheet.getDataRange().getValues();
// Store a rule to use for the Data Validation to be added if ColE == "Yes"
var rule = SpreadsheetApp.newDataValidation().requireValueInList(["Yes", "No"]).build();
// Loop the sheet
for(var i=0; i<data.length; i++) {
// Test ColE. Note that the array is 0-indexed, so A=0, B=2, etc...
// To change which columns you're testing, change the second value.
if(data[i][4] == "Yes") {
// If it's "Yes," add the Data Validation rule to Col G for that row.
// Note that .getRange() is _not_ 0 indexed, which is why you need `i+1` to get the correct row
sheet.getRange(i+1, 7).clear().setDataValidation(rule);
// If ColE == "No," mark ColG as "Not Applicable"
} else if(data[i][4] == "No") {
sheet.getRange(i+1, 7).clearDataValidations().setValue("Not Applicable");
}
}
}
Also note that this will change values as you change Col E. So, if you change a "Yes" to a "No," Col G will be changed to "Not Applicable."
for (var i = 0; i < 5000; i++) {
function checkData() {
if(SpreadsheetApp.getActiveSheet().getRange("E" + i).getValue() == "No"){
SpreadsheetApp.getActiveSheet().getRange("G" + i).setValue('Not Applicable');
}
else{
SpreadsheetApp.getActiveSheet().getRange("G" + i).setValue('Applicable');
}
}
}
Hope this works for you:)
Btw getting the range of 5000 rows will be VERY slow! Here is another way you can do it faster!
var ss = SpreadsheetApp.getActiveSpreadsheet();
var tsSheet = ss.getSheetByName("YOUR SHEET NAME AT THE BOTTOM OF THE PAGE");
var tsRows = parseInt(tsSheet.getLastRow());
for (var i = 0; i < tsRows; i++) {
function checkData() {
if(SpreadsheetApp.getActiveSheet().getRange("E" + i).getValue() == "No"){
SpreadsheetApp.getActiveSheet().getRange("G" + i).setValue('Not Applicable');
}
else{
SpreadsheetApp.getActiveSheet().getRange("G" + i).setValue('Applicable');
}
}
}
EDIT:
var ss = SpreadsheetApp.getActiveSpreadsheet();
var tsSheet = ss.getSheetByName("YOUR SHEET NAME AT THE BOTTOM OF THE PAGE");
var tsRows = parseInt(tsSheet.getLastRow());
for (var i = 0; i < tsRows + 1; i++) {
function checkData() {
if(SpreadsheetApp.getActiveSheet().getRange("E" + i).getValue() == "No"){
SpreadsheetApp.getActiveSheet().getRange("G" + i).setValue('Not Applicable');
}
else{
SpreadsheetApp.getActiveSheet().getRange("G" + i).setValue('Applicable');
}
}
}

for each in ss.getSheets() get a range of data

I'm struggling on a child task within a function I'm working on.
For each sheet in sheets, I would like to get the data in range A16:D, combine into one big array and then out put the combined data into a new sheet. But each time I try to select each data range it comes as undefined?
How can I get the data from each sheet in sheets and then add into one big array?
var ss = SpreadsheetApp.getActiveSpreadsheet();
var ranges = [];
function combineData() {
var activeSheetName = ss.getActiveSheet().getName();
var sheets = ss.getSheets();
sheets.forEach(function(e) {
var sheetName = e.getName();
if (sheetName != activeSheetName && sheetName != 'Report Configuration' && sheetName != 'Sheet1')
var wee_data = e.getRange('A16:D').getValues(); // TROUBLE HERE, WEE_DATA IS UNDEFINED
for(var j = 0; j<wee_data.length; j++) {
store.push(wee_data[j]);
}
ranges.push(wee_data);
});
if (ranges.length) {
Logger.log('range length is: ' + ranges.length);
adjustSheetLength(); // ensure there are enough rows in the destination sheet.
// figure out how to output data array "ranges" into a sheet "combined".
}
}
function adjustSheetLength(){
var comb = ss.getSheetByName('combined');
var lastRow = 4;
var maxRows = comb.getLastRow();
comb.deleteRows(lastRow, maxRows-lastRow);
var datapullSize = ranges.length;
comb.insertRows(4, datapullSize-2);// insert exactly the number of rows you need.
}
I think your issue is in the .forEach callback function. When I debugged those functions I got a not allowed in callback exception.
Change the foreach to a for in I think that'll fix your issue.
for( var i in sheets ) {
Logger.log(sheets[i]);
...
}