Google app scripts: Assign zero for empty cells - google-apps-script

I am new to Google Apps Script, trying to set values to a column based on its current value and a flag.
If Flag = Y then floor the value in C1:
C1 column value =23.9895
Expected value=23
If Flag = N then round the existing value in C1:
C1 column value =23.9895
Expected value=24
If the flag is either Y or N then write 0:
C1 column value=empty cell
Expected Value=0
I already implemented the below code. It was working partially. The first two scenarios works fine but the third scenario fails.
When I try to set zero, I am getting #NUM! error instead of zero. Not sure how to do it.
...
do {
sRange = "Q" + iCt;
if ((gDecimalInPrice == "Y") && (!isNaN(sheet.getRange(sRange).getValue()))) {
sheet.getRange(sRange).setValue(Math.abs(parseInt(sheet.getRange(sRange).getValue())));
} else if ((gDecimalInPrice == "N") && (!isNaN(sheet.getRange(sRange).getValue()))) {
sheet.getRange(sRange).setValue(Math.abs(Math.round(sheet.getRange(sRange).getValue())));
} else {
sheet.getRange(sRange).setValue(sheet.getRange(sRange).getValue());
}
iCt = iCt + 1;
} while (iCt <= gRowCt);

It's much faster to do this via batch operations (and follows official best practices). These read the values into a "2D" JavaScript array (an array of arrays of values), and then you can do all your logic in memory, rather than repeatedly requesting data from the slow Spreadsheet interface.
function foo() {
const wb = SpreadsheetApp.getActive();
const sheet = wb.getSheetByName("the sheet name");
if (!sheet) throw new Error("Sheet with that name is missing");
const lastRow = sheet.getLastRow();
const flags = sheet.getRange("A1:A" + lastRow).getValues();
const valueRange = sheet.getRange("Q1:Q" + lastRow);
const newValues = valueRange.getValues().map(function (row, i) {
return row.map(function (value) {
var flag = flags[i][0];
if (!flag || (value && isNaN(value))) // No "Y" or "N", or value is non-nullstring non-number, so return value as-is
return value;
else if (flag === "Y")
return value ? Math.floor(parseFloat(value)) : 0;
else if (flag === "N")
return value ? Math.round(parseFloat(value)) : 0;
else // Unknown flag value
return value;
});
});
// Write all processed values at once
valueRange.setValues(newValues);
}
As always, you should monitor macro and triggered functions for errors by reviewing your Stackdriver logs (accessible via the Script Editor's "View" menu).
Array#map

Related

Set value on form submission based on previous row

I have a script that triggers on form submission, checks the row before last row and should write a TRUE value to the 9th column of the checked row IF all three cells on its left have a TRUE value.
My problem is that the script always writes a FALSE value, even if the 3 cells on the left are all TRUE.
function onFormSubmit() {
var s = SpreadsheetApp.getActiveSpreadsheet();
var rcore = s.getSheetByName("test");
var lastrow = rcore.getLastRow();
var trgt = rcore.getRange(lastrow-1,9);
if(trgt.getValue() === ""){
if(trgt.offset(0, -3) == "TRUE" && trgt.offset(0, -2) == "TRUE" && trgt.offset(0, -1) == "TRUE"){
trgt.setValue("TRUE");
} else {
trgt.setValue("FALSE");
}
}
}
(My language is set to hungarian so that's why you see "IGAZ" for "TRUE" values and "HAMIS" for "FALSE" values)
The 3 TRUE/FALSE values are generated by ARRAYFORMULA. Maybe that is also important
SO FAR
I have tried several variations:
-tried to change the if to check with offset (0, -4) if its equal to 2 and not check anything else, but still I got FALSE values.
-I also tried to check with different if statements but it always gives FALSE.
-tried to check what happens if I also offset row to -1
I simply cant get a TRUE value. The last time I was able to get a TRUE value was when there was no other if statement, only one that checks if the cell is empty or not.
As Rubén pointed out, you need to use .getValue() to read the value in a range. You should also use the Boolean values true and false instead of the text strings "TRUE" and "FALSE", like this:
function onFormSubmit() {
const ss = SpreadsheetApp.getActive();
const rcore = ss.getSheetByName('test');
const lastRow = rcore.getlastRow();
const trgt = rcore.getRange(lastRow - 1, 9);
if (trgt.getValue() === '') {
if (trgt.offset(0, -3).getValue() === true && trgt.offset(0, -2).getValue() === true && trgt.offset(0, -1).getValue() === true) {
trgt.setValue(true);
} else {
trgt.setValue(false);
}
}
}
...or more concisely:
function onFormSubmit() {
const rcore = SpreadsheetApp.getActive().getSheetByName('test');
const trgt = rcore.getRange(rcore.getlastRow() - 1, 9);
if (trgt.getValue() === '') {
trgt.setValue(trgt.offset(0, -3, 1, 3).getValues().flat().every(value => value === true));
}
}

Use Google script function as formula - Error Exception: You do not have permission to call setActiveSheet

I'm trying to build a new formula using Google Script that loop through my rows to check if a value already exists, and then assign this value to a cell.
Street
#
Nb units
Barclay
3065
10
Barclay
3065
#ERROR!
This is my formula :
function check_address() {
var spreadsheet = SpreadsheetApp.getActive();
var pap = spreadsheet.setActiveSheet(spreadsheet.getSheetByName('Feuil1'), true)
var lastRow = pap.getLastRow(); //get last row
var nb_units = pap.getRange(lastRow, 3) //get the range where the value will go
var currentStreet = pap.getRange (lastRow, 1).getValue(); //check the last street name
var currentAddress = pap.getRange(lastRow, 2).getValue() // check the last street number
var data = pap.getDataRange().getValues();
for(var i = 0; i < data.length; i++){
if(currentStreet == data[i][0]){ // if the last address exists
units = data[i][2];
break // then the number of units for this address = Nb units of row [x]
}
else{
}
};
nb_units.setValue(units) // sets the range defined earlier
}
Problem I have is that when I use it as a formula I get this error : Exception: You do not have permission to call setActiveSheet (line 3).
Is there a way to modify the function so I can use it as a formula ?
Since you want to use this as custom formula it's easier to feed the range arguments to the Apps Script function and check from there:
function CHECKADDRESS(datarange, current) {
for (i = 0; i < datarange.length; i++) {
if (current[0][0] === datarange[i][0] && current[0][1] === datarange[i][1]) {
return datarange[i][2];
}
}
return null;
}
Sample Output:

Only one TRUE checkbox

I have a column of check boxes:
.
If a box is checked it sets a value to a cell in another sheet.
If I check box no.1 ,it turns true and the remaining still false
then if I check box no.2 it also turns true long with box no.1 and the remaining still false. This is the normal operation but I need that, when I check a box it turns true and all the other boxes turn false, either they are checked or not.In other words, I want one box to be checked at a time.
Can I do that?
This is my code to set a value if the box is checked:
var hasValue = sheet.getRange("B2:B").getValues();
for (var i = 0; i < hasValue.length; i++) {
if (hasValue[i][0] == true) {
var transfer = sheet2.getRange(2, 2, 1, 1).setValue(i + 1);
}
}
This kind of behavior is known as a "radio button".
The simplest method to achieve it is to bind the simple edit trigger:
inspect the edited range to determine if it was to your checkbox region and quit if not.
set all checkboxes to false
set the edited cell to the appropriate value from the event object
if required, perform the update
An extremely minimal sample which you will have to configure, and which is only configured for single-cell edits.
function onEdit(e) {
if (!e || e.value === undefined)
return; // The function was run from the Script Editor, or a multi-cell range was edited.
const edited = e.range;
const s = edited.getSheet();
if (s.getName() !== "some name")
return; // A cell on the wrong sheet was edited
if (isCheckboxRow_(edited.getRow()) && isCheckboxCol_(edited.getColumn())) {
// The cell edited was in a row and a column that contains a checkbox
updateCheckboxes_(s, edited, e);
}
}
function isCheckboxRow_(row) {
// Assumes checkboxes are only in rows 5, 6, 7, 8, 9, and 10
return row >= 5 && row <= 10;
}
function isCheckboxCol_(col) {
// Assumes checkboxes are in column A
return col === 1;
}
function updateCheckboxes_(sheet, editRange, eventObject) {
if (!sheet || !edit || !eventObject)
return; // Make sure all required arguments are defined (i.e. this was called and not run from the Script Editor)
const cbRange = sheet.getRange("A5:A10"); // location of the checkboxes in a radio group.
cbRange.setValue(false);
editRange.setValue(eventObject.value);
// Reference some other sheet
const targetSheet = eventObject.source.getSheetByName("some other sheet name")
if (!targetSheet)
return; // the sheet name didn't exist in the workbook we edited.
// Reference a cell in the same row as the cell we edited, in column 1
const targetCell = targetSheet.getRange(editRange.getRow(), 1);
if (eventObject.value) {
// when true, give the target cell the value of the cell next to the edited checkbox
targetCell.setValue(editRange.offset(0, 1).getValue());
// do other stuff that should be done when a checkbox is made true
} else {
// the checkbox was toggled to false, so clear the target cell
targetCell.clear();
// do other stuff that should be done when a checkbox is made false
}
}
The above hints at some suggested practices, such as using helper functions to encapsulate and abstract logic, resulting in easier to understand functions.
Review:
Simple Triggers
Event Objects
Spreadsheet Service
As I mentioned I would us an onEdit(event) to monitor which checkbox has been checked and loop through the column and only set one checkbox to true. Note that in your code snippet, getRange("B2:B") could be 999 rows. I use getDataRange() to limit to only the rows that are used. And I use getCriteriaType() to check that it is a checkbox not some other data type. And I'm assuming on your sheet2 you want to record which box was last checked true. tehhowch's answer is more generic and maybe more than what you need so here is a limited specific answer.
function onEdit(event) {
try {
var sheet = event.range.getSheet();
// Limit the following code to a particular sheet
if( sheet.getName() === "Sheet5" ) {
// Limit the following code to column B
if( event.range.getColumn() === 2 ) {
var range = sheet.getRange(2,2,sheet.getLastRow()-1,1);
var checks = range.getValues();
var valid = range.getDataValidations();
for( var i=0; i<checks.length; i++ ) {
if( valid[i][0].getCriteriaType() === SpreadsheetApp.DataValidationCriteria.CHECKBOX ) checks[i][0] = false;
}
// Assuming there are no formulas in this range
range.setValues(checks);
event.range.setValue(event.value);
if( event.value === true ) {
event.source.getSheetByName("Sheet6").getRange(2,2,1,1).setValue(event.range.getRow());
}
}
}
}
catch(err) {
SpreadsheetApp.getUi().alert(err);
}
}

What is the most efficient way to clear row if ALL cells have a value with Apps Script?

I'm trying to come up with a function that will clear contents (not delete row) if all cells in a range have values. The script below isn't functioning as expected, and I would really appreciate any help/advice you all have. It's currently only clearing out a single line, and doesn't appear to be iterating over the whole dataset. My thought was to iterate over the rows, and check each cell individually. If each of the variables has a value, clear that range and go to the next row.
Here's a link to a sample Google Sheet, with data and the script in Script Editor.
function MassRDDChange() {
// Google Sheet Record Details
var ss = SpreadsheetApp.openById('1bcrEZo3IkXiKeyD47C_k2LIRy9N9M6SI2h2MGK1Cj-w');
var dataSheet = ss.getSheetByName('Data Entry');
// Initial Sheet Values
var newLastColumn = dataSheet.getLastColumn();
var newLastRow = dataSheet.getLastRow();
var dataToProcess = dataSheet.getRange(2, 1, newLastRow, newLastColumn).getValues().filter(function(row) {
return row[0]
}).sort();
var dLen = dataToProcess.length;
// Clear intiial sheet
for (var i = 0; i < dLen; ++i) {
var row = 2;
var orderNumber = dataToProcess[i][0].toString();
var rdd = dataToProcess[i][1].toString();
var submittedBy = dataToProcess[i][2].toString();
var submittedOn = dataToProcess[i][3].toString();
if (orderNumber && rdd && submittedBy && submittedOn) {
dataSheet.getRange(row, 1, 1, newLastColumn).clear();
row++;
} else {
row++; // Go to the next row
continue;
}
}
}
Thanks!
Since you don't want to delete the rows, just clear() them, and they're all on the same worksheet tab, this is a great use case for RangeLists, which allow you to apply specific Range methods to non-contiguous Ranges. Currently, the only way to create a RangeList is from a an array of reference notations (i.e. a RangeList is different than an array of Range objects), so the first goal we have is to prefix our JavaScript array of sheet data to inspect with a usable reference string. We could write a function to convert array indices from 0-base integers to A1 notation, but R1C1 referencing is perfectly valid to pass to the RangeList constructor, so we just need to account for header rows and the 0-base vs 1-base indexing difference.
The strategy, then, is to:
Batch-read sheet data into a JavaScript Array
Label each element of the array (i.e. each row) with an R1C1 string that identifies the location where this element came from.
Filter the sheet data array based on the contents of each element
Keep elements where each sub-element (the column values in that row) converts to a boolean (i.e., does not have the same value as an empty cell)
Feed the labels of each of the kept rows to the RangeList constructor
Use RangeList methods on the RangeList
Because this approach uses only 3 Spreadsheet calls (besides the initial setup for a batch read), vs 1 per row to clear, it should be considerably faster.
function clearFullyFilledRows() {
// Helper function that counts the number of populated elements of the input array.
function _countValues(row) {
return row.reduce(function (acc, val) {
var hasValue = !!(val || val === false || val === 0); // Coerce to boolean
return acc + hasValue; // true == 1, false == 0
}, 0);
}
const sheet = SpreadsheetApp.getActiveSheet();
const numHeaderRows = 1,
numRows = sheet.getLastRow() - numHeaderRows;
const startCol = 1,
numCols = sheet.getLastColumn();
// Read all non-header sheet values into a JavaScript array.
const values = sheet.getSheetValues(1 + numHeaderRows, startCol, numRows, numCols);
// From these values, return a new array where each row is the origin
// label and the count of elements in the original row with values.
const labeledCounts = values.map(function(row, index) {
var rNc = "R" + (numHeaderRows + 1 + index) + "C";
return [
rNc + startCol + ":" + rNc + (startCol + numCols - 1),
_countValues(row)
];
});
// Filter out any row that is missing a value.
const toClear = labeledCounts.filter(function (row) { return row[1] === numCols; });
// Create a RangeList from the first index of each row (the R1C1 label):
const rangeList = sheet.getRangeList(toClear.map(function (row) { return row[0]; }));
// Clear them all:
rangeList.clear();
}
Note that because these cleared rows are possibly disjoint, your resulting sheet may be littered with rows having data, and rows not having data. A call to sheet.sort(1) would sort all the non-frozen rows in the sheet, moving the newly-empty rows to the bottom (yes, you can programmatically set frozen rows). Depending how this sheet is referenced elsewhere, that may not be desirable though.
Additional references:
Array#filter
Array#reduce
Array#map
JavaScript Logical Operators
JavaScript Comparison Operators

Vlookup or Indexing using google apps scripts

I have a spreadshseet (Sheet1) in which the Data is there from Col A to Column D, and in another sheet (Sheet1), again the data is there from Col A to Col W, in which the Col F data has some matching with column D.
What i am seeking for:
I want to pull data from Sheet2 (from Col F onwards, i.e. G, H, I etc.) in Col E and so on in Sheet1.
Sheet2
Col
F G H I J K L
1 A B C D E F
2 a1 b1 c1 d1 e1 f1
3 a2 b2 c2 d2 e2 f2
and so on
Sheet1
Col D E F G H
1 A B C D
3 a2 b2 c2 d2
Data to reflect in col E,F,G H in sheet1 from sheet2 against column D in col E,F, either using vlookup, or indexing.
What i tried but in Vain
http://productforums.google.com/forum/#!topic/apps-script/HzeNdIqnIUc
I want to use only google apps only to get the desired results.
Requesting for help on this.
Regards
I'm sorry you're having a problem with your VLOOKUP but it's a Google Spreadsheets user question not a stackoverflow (programming) question. It might help you to know these things:
You're on the right track with VLOOKUP. The spreadsheet function VLOOKUP definitely works for getting data in sheet2 to lookup based on keys/columns in sheet1. It works really well for what you say you need to do.
You can search the Google support site: http://support.google.com/docs/bin/search.py?query=vlookup
You can ask in the Google Docs Help forum https://productforums.google.com/forum/#!forum/docs
Good luck.
Its an old article but others may still stumble across it.
Here is something I wrote to address my Vlookup Needs in script form.
//~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`
//--//Dependent on isEmpty_()
// Script Look-up
/*
Benefit of this script is:
-That google sheets will not continually do lookups on data that is not changing with using this function as it is set with hard values until script is kicked off again.
-Unlike Vlookup you can have it look at for reference data at any Column in the row. Does not have to be in the first column for it to work like Vlookup.
-You can return the Lookup to Memory for further processing by other functions
Useage:
var LocNum = SpreadsheetApp.openById(SheetID).getSheetByName('Sheet1').getRange('J2:J').getValues();
Lookup_(Sheetinfo,"Sheet1!A:B",0,[1],"Sheet1!I1","n","y");
//or
Lookup_(Sheetinfo,"Sheet1!A:B",0,[1],"return","n","n");
//or
Lookup_(Sheetinfo,"Sheet1!A:B",0,[0,1],"return","n","n");
//or
Lookup_(Sheetinfo,"Sheet1!A:B",1,[0],"return","y","n");
//or
Lookup_(Sheetinfo,"Sheet1!A:G",4,[0],"Database!A1","y","y");
*/
function Lookup_(Search_Key,RefSheetRange,SearchKey_Ref_IndexOffSet,IndexOffSetForReturn,SetSheetRange,ReturnMultiResults,Add_Note)
{
var RefSheetRange = RefSheetRange.split("!");
var Ref_Sheet = RefSheetRange[0];
var Ref_Range = RefSheetRange[1];
if(!/return/i.test(SetSheetRange))
{
var SetSheetRange = SetSheetRange.split("!");
var Set_Sheet = SetSheetRange[0];
var Set_Range = SetSheetRange[1];
var RowVal = SpreadsheetApp.getActive().getSheetByName(Set_Sheet).getRange(Set_Range).getRow();
var ColVal = SpreadsheetApp.getActive().getSheetByName(Set_Sheet).getRange(Set_Range).getColumn();
}
var twoDimensionalArray = [];
var data = SpreadsheetApp.getActive().getSheetByName(Ref_Sheet).getRange(Ref_Range).getValues(); //Syncs sheet by name and range into var
for (var i = 0, Il=Search_Key.length; i<Il; i++) // i = number of rows to index and search
{
var Sending = []; //Making a Blank Array
var newArray = []; //Making a Blank Array
var Found ="";
for (var nn=0, NNL=data.length; nn<NNL; nn++) //nn = will be the number of row that the data is found at
{
if(Found==1 && ReturnMultiResults.toUpperCase() == 'N') //if statement for found if found = 1 it will to stop all other logic in nn loop from running
{
break; //Breaking nn loop once found
}
if (data[nn][SearchKey_Ref_IndexOffSet]==Search_Key[i]) //if statement is triggered when the search_key is found.
{
var newArray = [];
for (var cc=0, CCL=IndexOffSetForReturn.length; cc<CCL; cc++) //cc = numbers of columns to referance
{
var iosr = IndexOffSetForReturn[cc]; //Loading the value of current cc
var Sending = data[nn][iosr]; //Loading data of Level nn offset by value of cc
if(isEmpty_(Sending)) //if statement for if one of the returned Column level cells are blank
{
var Sending = "#N/A"; //Sets #N/A on all column levels that are blank
}
if (CCL>1) //if statement for multi-Column returns
{
newArray.push(Sending);
if(CCL-1 == cc) //if statement for pulling all columns into larger array
{
twoDimensionalArray.push(newArray);
var Found = 1; //Modifying found to 1 if found to stop all other logic in nn loop
break; //Breaking cc loop once found
}
}
else if (CCL<=1) //if statement for single-Column returns
{
twoDimensionalArray.push(Sending);
var Found = 1; //Modifying found to 1 if found to stop all other logic in nn loop
break; //Breaking cc loop once found
}
}
}
if(NNL-1==nn && isEmpty_(Sending)) //following if statement is for if the current item in lookup array is not found. Nessessary for data structure.
{
for(var na=0,NAL=IndexOffSetForReturn.length;na<NAL;na++) //looping for the number of columns to place "#N/A" in to preserve data structure
{
if (NAL<=1) //checks to see if it's a single column return
{
var Sending = "#N/A";
twoDimensionalArray.push(Sending);
}
else if (NAL>1) //checks to see if it's a Multi column return
{
var Sending = "#N/A";
newArray.push(Sending);
}
}
if (NAL>1) //checks to see if it's a Multi column return
{
twoDimensionalArray.push(newArray);
}
}
}
}
if (CCL<=1) //checks to see if it's a single column return for running setValue
{
var singleArrayForm = [];
for (var l = 0,lL=twoDimensionalArray.length; l<lL; l++) //Builds 2d Looping-Array to allow choosing of columns at a future point
{
singleArrayForm.push([twoDimensionalArray[l]]);
}
if(!/return/i.test(SetSheetRange))
{
SpreadsheetApp.getActive().getSheetByName(Set_Sheet).getRange(RowVal,ColVal,singleArrayForm.length,singleArrayForm[0].length).setValues(singleArrayForm);
SpreadsheetApp.flush();
if(/y/i.test(Add_Note))
{
SpreadsheetApp.getActive().getSheetByName(Set_Sheet).getRange(RowVal,ColVal,1,1).setNote("VLookup Script Ran On: " + Utilities.formatDate(new Date(), "PST", "MM-dd-yyyy hh:mm a") + "\nRange: " + Ref_Sheet + "!" + Ref_Range);
}
}
else
{
return singleArrayForm
}
}
if (CCL>1) //checks to see if it's a multi column return for running setValues
{
if(!/return/i.test(SetSheetRange))
{
SpreadsheetApp.getActive().getSheetByName(Set_Sheet).getRange(RowVal,ColVal,twoDimensionalArray.length,twoDimensionalArray[0].length).setValues(twoDimensionalArray);
SpreadsheetApp.flush();
if(/y/i.test(Add_Note))
{
SpreadsheetApp.getActive().getSheetByName(Set_Sheet).getRange(RowVal,ColVal,1,1).setNote("VLookup Script Ran On: " + Utilities.formatDate(new Date(), "PST", "MM-dd-yyyy hh:mm a") + "\nRange: " + Ref_Sheet + "!" + Ref_Range);
}
}
else
{
return twoDimensionalArray
}
}
}
//~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`
//~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`
// Empty String Check
function isEmpty_(string)
{
if(Object.prototype.toString.call(string) == '[object Boolean]') return false;
if(!string) return true;
if(string == '') return true;
if(string === false) return true;
if(string === null) return true;
if(string == undefined) return true;
string = string+' '; // check for a bunch of whitespace
if('' == (string.replace(/^\s\s*/, '').replace(/\s\s*$/, ''))) return true;
return false;
}
//~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`