BarCode Maker (pulling data from other sheets) - google-apps-script

I am working on a function that will construct a bar code from a few data points within my spreadsheet for items in an inventory.
My issue/question is two fold:
When running through the for loop, I am getting this error message "TypeError: Cannot read property "length" from null."
While it looks like I have successfully loaded the ranges I want into veritable, I am not entirely sure as to how to call them, could you please tell me if I am calling them correctly within the if statement?
function barCodeBuilder(stockName) {
// Setting Ranges from prices table
var sheet = SpreadsheetApp.getActive();
var listNameRange = sheet.getRangeByName('MeterailsPricing!ITEM NAME');
var listSkuRange = sheet.getRangeByName('MeterailsPricing!INVENTORY SKU');
var listPriceRange = sheet.getRangeByName('MeterailsPricing!PRICE');
var listSupRange = sheet.getRangeByName('MeterailsPricing!SUPPLIER CODE');
//Errors begin here = TypeError: Cannot read property "length" from null
for(listCounter = 0; listCounter < listNameRange.length; listCounter++){
if(stockName == listNameRange[listCounter][0]){
var barCode = {
sku:listSkuRange[listCounter][0],
supCode:listSupRange[listCounter][0],
price:listPriceRange[listCounter][0]};
}
}
//final bar code assembly
var finalBarCode = barCode.sku + "-" + barCode.supCode + "-" + barCode.price;
return finalBarCode;
}
And as always, thank you all for taking the time to help me out.
Talk to you soon.
Greg R.

Error "TypeError: Cannot read property "length" from null." occurred if there were no matches for the regular expression. Because of this, listNameRange will be null. You need to check for this before your loop.
Sample code snippet from this SO answer:
if (templateVars !== null) {
for (var i = 0; i < templateVars.length; i++) {
...
}
}
Here are some related SO posts which might help:
TypeError: Cannot read property "length" from null
Google Spreadsheets script to delete any rows where a string is found

Related

Apps Script custom function working in script editor but not in Google Sheet custom function

I have built a simple custom function in Apps Script using URLFetchApp to get the follower count for TikTok accounts.
function tiktok_fans() {
var raw_data = new RegExp(/("followerCount":)([0-9]+)/g);
var handle = '#charlidamelio';
var web_content = UrlFetchApp.fetch('https://www.tiktok.com/'+ handle + '?lang=en').getContentText();
var match_text = raw_data.exec(web_content);
var result = (match_text[2]);
Logger.log(result)
return result
}
The Log comes back with the correct number for followers.
However, when I change the code to;
function tiktok_fans(handle) {
var raw_data = new RegExp(/("followerCount":)([0-9]+)/g);
//var handle = '#charlidamelio';
var web_content = UrlFetchApp.fetch('https://www.tiktok.com/'+ handle + '?lang=en').getContentText();
var match_text = raw_data.exec(web_content);
var result = (match_text[2]);
Logger.log(result)
return result
}
and use it in a spreadsheet for example =tiktok_fans(A1), where A1 has #charlidamelio I get an #ERROR response in the cell
TypeError: Cannot read property '2' of null (line 6).
Why does it work in the logs but not in the spreadsheet?
--additional info--
Still getting the same error after testing #Tanaike answer below, "TypeError: Cannot read property '2' of null (line 6)."
Have mapped out manually to see the error, each time the below runs, a different log returns "null". I believe this is to do with the ContentText size/in the cache. I have tried utilising Utilities.sleep() in between functions with no luck, I still get null's.
code
var raw_data = new RegExp(/("followerCount":)([0-9]+)/g);
//tiktok urls
var qld = UrlFetchApp.fetch('https://www.tiktok.com/#thisisqueensland?lang=en').getContentText();
var nsw = UrlFetchApp.fetch('https://www.tiktok.com/#visitnsw?lang=en').getContentText();
var syd = UrlFetchApp.fetch('https://www.tiktok.com/#sydney?lang=en').getContentText();
var tas = UrlFetchApp.fetch('https://www.tiktok.com/#tasmania?lang=en').getContentText();
var nt = UrlFetchApp.fetch('https://www.tiktok.com/#ntaustralia?lang=en').getContentText();
var nz = UrlFetchApp.fetch('https://www.tiktok.com/#purenz?lang=en').getContentText();
var aus = UrlFetchApp.fetch('https://www.tiktok.com/#australia?lang=en').getContentText();
var vic = UrlFetchApp.fetch('https://www.tiktok.com/#visitmelbourne?lang=en').getContentText();
//find folowers with regex
var match_qld = raw_data.exec(qld);
var match_nsw = raw_data.exec(nsw);
var match_syd = raw_data.exec(syd);
var match_tas = raw_data.exec(tas);
var match_nt = raw_data.exec(nt);
var match_nz = raw_data.exec(nz);
var match_aus = raw_data.exec(aus);
var match_vic = raw_data.exec(vic);
Logger.log(match_qld);
Logger.log(match_nsw);
Logger.log(match_syd);
Logger.log(match_tas);
Logger.log(match_nt);
Logger.log(match_nz);
Logger.log(match_aus);
Logger.log(match_vic);
Issue:
From your situation, I remembered that the request of UrlFetchApp with the custom function is different from the request of UrlFetchApp with the script editor. So I thought that the reason for your issue might be related to this thread. https://stackoverflow.com/a/63024816 In your situation, your situation seems to be the opposite of this thread. But, it is considered that this issue is due to the specification of the site.
In order to check this difference, I checked the file size of the retrieved HTML data.
The file size of HTML data retrieved by UrlFetchApp executing with the script editor is 518k bytes.
The file size of HTML data retrieved by UrlFetchApp executing with the custom function is 9k bytes.
It seems that the request of UrlFetchApp executing with the custom function is the same as that of UrlFetchApp executing withWeb Apps. The data of 9k bytes are retrieved by using this.
From the above result, it is found that the retrieved HTML is different between the script editor and the custom function. Namely, the HTML data retrieved by the custom function doesn't include the regex of ("followerCount":)([0-9]+). By this, such an error occurs. I thought that this might be the reason for your issue.
Workaround:
When I tested your situation with Web Apps and triggers, the same issue occurs. By this, in the current stage, I thought that the method for automatically executing the script might not be able to be used. So, as a workaround, how about using a button and the custom menu? When the script is run by the button and the custom menu, the script works. It seems that this method is the same as that of the script editor.
The sample script is as follows.
Sample script:
Before you run the script, please set range. For example, please assign this function to a button on Spreadsheet. When you click the button, the script is run. In this sample, it supposes that the values like #charlidamelio are put to the column "A".
function sample() {
var range = "A2:A10"; // Please set the range of "handle".
var raw_data = new RegExp(/("followerCount":)([0-9]+)/g);
var sheet = SpreadsheetApp.getActiveSheet();
var r = sheet.getRange(range);
var values = r.getValues();
var res = values.map(([handle]) => {
if (handle != "") {
var web_content = UrlFetchApp.fetch('https://www.tiktok.com/'+ handle + '?lang=en').getContentText();
var match_text = raw_data.exec(web_content);
return [match_text[2]];
}
return [""];
});
r.offset(0, 1).setValues(res);
}
When this script is run, the values are retrieved from the URL and put to the column "B".
Note:
This is a simple script. So please modify it for your actual situation.
Reference:
Related thread.
UrlFetchApp request fails in Menu Functions but not in Custom Functions (connecting to external REST API)
Added:
About the following additional question,
whilst this works for 1 TikTok handle, when trying to run a list of multiple it fails each time, with the error TypeError: Cannot read property '2' of null. After doing some investigating and manually mapping out 8 handles, I can see that each time it runs, it returns "null" for one or more of the web_content variables. Is there a way to slow the script down/run each UrlFetchApp one at a time to ensure each returns content?
i've tried this and still getting an error. Have tried up to 10000ms. I've added some more detail to the original question, hope this makes sense as to the error. It is always in a different log that I get nulls, hence why I think it's a timing or cache issue.
In this case, how about the following sample script?
Sample script:
In this sample script, when the value cannot be retrieved from the URL, the value is tried to retrieve again as the retry. This sample script uses the 2 times as the retry. So when the value cannot be retrieved by 2 retries, the empty value is returned.
function sample() {
var range = "A2:A10"; // Please set the range of "handle".
var raw_data = new RegExp(/("followerCount":)([0-9]+)/g);
var sheet = SpreadsheetApp.getActiveSheet();
var r = sheet.getRange(range);
var values = r.getValues();
var res = values.map(([handle]) => {
if (handle != "") {
var web_content = UrlFetchApp.fetch('https://www.tiktok.com/'+ handle + '?lang=en').getContentText();
var match_text = raw_data.exec(web_content);
if (!match_text || match_text.length != 3) {
var retry = 2; // Number of retry.
for (var i = 0; i < retry; i++) {
Utilities.sleep(3000);
web_content = UrlFetchApp.fetch('https://www.tiktok.com/'+ handle + '?lang=en').getContentText();
match_text = raw_data.exec(web_content);
if (match_text || match_text.length == 3) break;
}
}
return [match_text && match_text.length == 3 ? match_text[2] : ""];
}
return [""];
});
r.offset(0, 1).setValues(res);
}
Please adjust the value of retry and Utilities.sleep(3000).
This works for me as a Custom Function:
function MYFUNK(n=2) {
const url = 'my website url'
const re = new RegExp(`<p id="un${n}.*\/p>`,'g')
const r = UrlFetchApp.fetch(url).getContentText();
const v = r.match(re);
Logger.log(v);
return v;
}
I used my own website and I have several paragraphs with ids from un1 to un7 and I'm taking the value of A1 for the only parameter. It returns the correct string each time I change it.

How do I handle an exception that's being thrown when using SpreadsheetApp.openById()

I'm very new to coding of any kind and search as I might I wasn't able to find a solution for this exception:
Exception: Unexpected error while getting the method or property openById on object SpreadsheetApp. (line 16)
The script I've written copies each file from a list of file IDs, pastes entire contents as values into the copied files.
What I've already done is check if there was an authorisation required in oauthScope in my appscript.json and I think I've given sufficient authorisation (spreadsheet and drive). I don't get any other permission prompts.
Any help or suggestions will be great (generally for what I've written, but importantly for the exception)
This is the script:
function copyPasteValues()
{
//Define destination archive folder
var Destination = DriveApp.getFolderById("ID");
//Open correct sheet in the Archiving Center file and get ID range
var AllFileID_ss = SpreadsheetApp.openById("ID_2"); *------- no error on this one*
var AllFileID_sheet = AllFileID_ss.getSheetByName("Sheets_to_be_archived");
var FileID = AllFileID_sheet.getRange("B2:B10").getValues();
//Select the correct file to archive and create a copy
for (var i=0; i<FileID.length;i++)
{
if (FileID != "-")
{
var archfile = SpreadsheetApp.openById(FileID[i]); *------------ exception being thrown here (line 16)*
var archcopy = DriveApp.getFileById(FileID[i]).makeCopy("Archive "+archfile.getName(), Destination);
var copyId = archcopy.getId();
var sheetNumber = archfile.getSheets().length;
//Select correct sheets in copy, and paste values
for (var j=0; j<sheetNumber;j++)
{
var values = archfile.getSheets()[j].getDataRange().getValues();
SpreadsheetApp.openById(copyId).getSheets()[j].getRange(archfile.getSheets()[j].getDataRange().getA1Notation()).setValues(values);
}
}
}
Browser.msgBox("Archiving is done");
}
P.S. The script runs fine otherwise and carries out the copy and archive despite the exception, but the last line which is a msgBox does not display the message. I do not know why.
As per documentation, openById(id) method signature expects a string as a parameter. Your FileID variable holds an array of arrays of values returned by the getValues() method call (see docs), hence accessing an item from it by index [i] returns an array, and not the value.
The condition FileID != "-" is always evaluated to true in your script, since you are doing a strict comparison that checks for inequality in your case. It returns true as soon a type mismatch happens: "object" !== "string".
const whatGetValuesReturns = [
[1, 2, 3, 4, 5],
[6, 7, 8, 9, 10]
];
const whatOpenByIdExpects = "string";
for(var i = 0; i < whatGetValuesReturns.length; i++) {
const whatEachIterationOfLoopGets = whatGetValuesReturns[i];
const typeOfWhatIsExtracted = typeof whatEachIterationOfLoopGets;
console.log(
`Expected ${whatOpenByIdExpects},
got ${typeOfWhatIsExtracted} with values:
${whatEachIterationOfLoopGets.join(" ")}`
);
}
Based on the discussion, I need to clarify that when you limit getRange() to a single column (off-note: B:B would be a column reference, B2:B10 is a range of cells) it still returns a Range instance, which in turn has a getValues() method defined that always returns a matrix of values:
const singleColumn = [
[1],
[2],
[3],
[4]
];
for(const row of singleColumn) {
const value = row[0]; //<-- since we have only one, first index is used
console.log(value);
}
As you #Oleg Valter rightly pointed out, I had tried to pass what I thought would be a string value, but was actually an array, into openbyID(id). Your comment below clarified it.
I see you have an assumption that limiting the range to a column would get you only the column, but that's not how programs work - they are very stupid. You told the script to get you a list rows where each row is a list (array) with only one item, and that's what it returned
I modified this part of the script as below and it worked:
var AllFileID = AllFileID_sheet.getRange("B2:B10").getValues();
//Select the correct file to archive and create a copy
for (var i=0; i<AllFileID.length;i++)
{
var FileID = AllFileID[i][0] *----this is the change to pick out the value from the array*
if (FileID != "-")
{
var archfile = SpreadsheetApp.openById(FileID); *---works!*
Thanks very much!

How do I resolve OAuth scopes to GSheets and in call out to a GClassroom function, in order to grab assignment data and post to GSheet?

I don't know JSON, so I'm trying to code this with GScript. I want to combine a call out to this function that gets Classroom info from a working script function that posts array info to a GSheet.
The first time I ran the script below, I triggered the API authentication and got the information I needed, although only in Logger.
var email = "my_email#something.org";
function countWork(email) {
var courseId = "valid_courseId";
var data = ""; // String of resulting information from loop below
var assignments = Classroom.Courses.CourseWork.list(courseId);
var length = assignments.courseWork.length;
// Loop to gather info
for (j = 0; j < length; j++) {
var assignment = assignments.courseWork[j];
var title = assignment.title;
var created = assignment.creationTime;
Logger.log('-->Assignment No. %s -->%s -->(%s)',j+1,title,created);
}
return data;
}
But for some reason, I can't OAuth scopes on this version of the script where I've substituted the array I need for posting to GSheet. I get the error message "Classroom is not defined (line 7...)." What do I need to do so Classroom.Courses.etc will be recognized?
var email = "my_email#something.org";
function extractAssignmentData(email) {
var courseId = "valid_courseId"; //
var data = []; // Array of resulting information from loop below
var assignments = Classroom.Courses.CourseWork.list(courseId); // error: Classroom is not defined (line 7)
var length = assignments.courseWork.length;
// Loop to gather data
for (j = 0; j < length; j++) {
var assignment = assignments.courseWork[j];
// types of information: description, creationTime, updateTime, dueDate, dueTime, workType
var title = assignment.title;
var created = assignment.creationTime;
var info = [j+1,title,created];
data.push(info);
}
return data;
}
Thanks so much, Tanaike, for your helpful responses!
Based on your suggestions, I was able to find this post, which explicitly described how to consult the manifest file, and how to incorporate scopes into the appsscript.json.
I'm still not sure why the first version of the script triggered the scope
"https://www.googleapis.com/auth/classroom.coursework.students"
while the second instead added this one:
"https://www.googleapis.com/auth/classroom.coursework.me.readonly"
But, since I now know how to add what I need and can access the Classroom info I need, it's a mute point. Thanks, again!
(I'm not sure how to mark your comment as the answer to my question -- you should get the points!)

Detect formula errors in Google Sheets using Script

My ultimate goal is here, but because I've gotten no replies, I'm starting to learn things from scratch (probably for the best anyway). Basically, I want a script that will identify errors and fix them
Well, the first part of that is being able to ID the errors. Is there a way using Google Script to identify if a cell has an error in it, and return a particular message as a result? Or do I just have to do an if/else that says "if the cell value is '#N/A', do this", plus "if the cell value is '#ERROR', do this", continuing for various errors?. Basically I want ISERROR(), but in the script
Use a helper function to abstract away the nastiness:
function isError_(cell) {
// Cell is a value, e.g. came from `range.getValue()` or is an element of an array from `range.getValues()`
const errorValues = ["#N/A", "#REF", .... ];
for (var i = 0; i < errorValues.length; ++i)
if (cell == errorValues[i])
return true;
return false;
}
function foo() {
const vals = SpreadsheetApp.getActive().getSheets()[0].getDataRange().getValues();
for (var row = 0; row < vals.length; ++row) {
for (var col = 0; col < vals[0].length; ++col) {
if (isError_(vals[row][col])) {
Logger.log("Array element (" + row + ", " + col + ") is an error value.");
}
}
}
}
Using Array#indexOf in the helper function:
function isError_(cell) {
// Cell is a value, e.g. came from `range.getValue()` or is an element of an array from `range.getValues()`
// Note: indexOf uses strict equality when comparing cell to elements of errorValues, so make sure everything's a primitive...
const errorValues = ["#N/A", "#REF", .... ];
return (errorValues.indexOf(cell) !== -1);
}
If/when Google Apps Script is upgraded with Array#includes, that would be a better option than Array#indexOf:
function isError_(cell) {
// cell is a value, e.g. came from `range.getValue()` or is an element of an array from `range.getValues()`
const errorValues = ["#N/A", "#REF", .... ];
return errorValues.includes(cell);
}
Now that the v8 runtime is available, there are a number of other changes one could make to the above code snippets (arrow functions, etc) but note that changing things in this manner is not required.
Update: 25 March 2020
#tehhowch remarked "If/when Google Apps Script is upgraded with Array#includes, that would be a better option than Array#indexOf".
Array.includes does now run in Apps Script and, as anticipated provides a far more simple approach when compared to indexOf.
This example varies from the previous answers by using a specific range to show that looping through each cell is not required. In fact, this answer will apply to any range length.
The two key aspects of the answer are:
map: to create an array for each column
includes: used in an IF statement to test for a true or false value.
function foo() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sourcename = "source_sheet";
var source = ss.getSheetByName(sourcename);
var sourcerange = source.getRange("A2:E500");
var sourcevalues = sourcerange.getValues();
// define the error values
var errorValues = ["#NULL!", "#DIV/0!", "#VALUE!", "#REF!", "#NAME?", "#NUM!", "#N/A","#ERROR!"];
// loop though the columns
for (var c = 0;c<5;c++){
// create an array for the column
var columnoutput = sourcevalues.map(function(e){return e[c];});
// loop through errors
for (var errorNum=0; errorNum<errorValues.length;errorNum++){
// get the error
var errorvalue = errorValues[errorNum]
// Logger.log("DEBUG: column#:"+c+", error#:"+e+", error value = "+errorvalue+", does col include error = "+columnoutput.includes(errorvalue));
// if the error exists in this column then resposnse = true, if the error doesn't exist then response = false
if (columnoutput.includes(errorvalue) != true){
Logger.log("DEBUG: Column#:"+c+", error#:"+errorNum+"-"+errorvalue+" - No ERROR");
} else {
Logger.log("DEBUG: column#:"+c+", error#:"+errorNum+"-"+errorvalue+"- ERROR EXISTS");
}
}
}
return;
}
Shorter yet, use a nested forEach() on the [vals] array, then check to see if the cell value matches a value of the [errorValues] array with indexOf. This was faster than a for loop...
function foo() {
const errorValues = ["#NULL!", "#DIV/0!", "#VALUE!", "#REF!", "#NAME?", "#NUM!", "#N/A","#ERROR!"];
const vals = SpreadsheetApp.getActive().getSheets()[0].getDataRange().getValues();
vals.forEach((val,row) => { val.forEach((item, col) => {
(errorValues.indexOf(item) !== -1) ? Logger.log("Array element (" + row + ", " + col + ") is an error value.") : false ;
});
});
}
I had a similar question and resolved using getDisplayValue() instead of getValue()
Try something like:
function checkCells(inputRange) {
var inputRangeCells = SpreadsheetApp.getActiveSheet().getRange(inputRange);
var cellValue;
for(var i=0; i < inputRangeCells.length; i++) {
cellValue = inputRangeCells[i].getDisplayValue();
if (cellValue=error1.....) { ... }
}
}
Display value should give you what's displayed to the user rather than #ERROR!

Type Error: Cannot read property "length" from undefined in Google Apps Script

I'm trying to use #serge 's script to pull emails into a spreadsheet.
I'm getting a type error: "Cannot read property "length" from undefined".
Any help figuring out the problem would be much appreciated.
function getMessagesWithLabel() {
var destArray = new Array();
var threads = GmailApp.getUserLabelByName('CDC Health Alerts').getThreads(1,30);
for(var n in threads){
var msg = threads[n].getMessages();
var destArrayRow = new Array();
destArrayRow.push('thread has '+threads[n].getMessageCount()+' messages');
for(var m in msg){
destArrayRow.push(msg[m].getSubject());
}
destArray.push(destArrayRow);
}
Logger.log(destArray);
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getActiveSheet();
if(ss.getLastRow()==0){
sh.getRange(1,1).setValue('getMessagesWithLabel() RESULTS')
};
sh.getRange(ss.getLastRow()+1,1,destArray.length,destArray[0].length).setValues(destArray)
}
The only line that the length() property is used on, is the very last line.
sh.getRange(ss.getLastRow()+1,1,destArray.length,destArray[0].length).setValues(destArray)
So the array named destArray is not getting any data pushed into it. If you check the value that destArray has in it, it will show a value of undefined. You will need to start at the top of the code, and check critical points for what is getting assigned to variables. For example:
var threads = GmailApp.getUserLabelByName('CDC Health Alerts').getThreads(1,30);
Logger.log('threads: ' + threads);
Note the Logger.log() statement in the above code. Add a Logger.log('Some Text: ' + variableName); statement, then VIEW the LOGS, and see if the variable 'threads` is getting what is needed assigned to it.
Keep doing this until you find a problem. Even though the error is coming from the last line, there is an error higher up in the code, because the variable destArray is not getting assigned values.