Error Parsing Google Maps Data from XML using Apps Script in Google Sheets - google-maps

I'm trying to extract the "formatted_address" from an XML that is created using a Google Maps API. I can't get past the XML part of the code. I'm getting the error "Exception: Bad request: http://0 (line 176)". Is there something I'm missing? How can I modifiy this? Any guidance is appreciated.
Line 176 is "var extractXml = UrlFetchApp.fetch(addUrl);"
The idea is to look up a "sloppy" address and return a Google standardized version in another cell in Google Sheets.
The snip is the XML as seen in browser when I paste in the URL. The "formatted_address" part is what I'm trying to return.
function addressFormat() {
var ss = SpreadsheetApp.getActive();
var sss = ss.getSheetByName("format");
var addr = sss.getRange(1,1,).getValue();
var apiKey = "API Key Here";
var addUrl = "https://maps.googleapis.com/maps/api/geocode/xml?address=" & addr & "&key=" & apiKey;
var extractXml = UrlFetchApp.fetch(addUrl);
var contentsXml = extractXml.getContentText;
var xmlAdd = XmlService.parse(contentsXml);
var response = xmlAdd.getRootElement();
var records = response.getChild('GeocodeResponse');
var recordList = records.getChild('result');
var formattedAddd = recordList('formatted_address')
return formattedAddd.getValue;
}

Modification points:
I thought that var addUrl = "https://maps.googleapis.com/maps/api/geocode/xml?address=" & addr & "&key=" & apiKey; should be var addUrl = "https://maps.googleapis.com/maps/api/geocode/xml?address=" + encodeURIComponent(addr) + "&key=" + apiKey; or var addUrl = `https://maps.googleapis.com/maps/api/geocode/xml?address=${encodeURIComponent(addr)}&key=${apiKey}`;.
Please add () to var contentsXml = extractXml.getContentText;.
From your script, I thought that you might want to retrieve the value of formatted_address. In this case, it is required to modify your script for retrieving the value from XML data.
When these points are reflected in your script, how about the following modification?
Modified script:
function addressFormat() {
var apiKey = "API Key Here"; // Please set your API key.
var ss = SpreadsheetApp.getActive();
var sss = ss.getSheetByName("format");
var addr = sss.getRange(1, 1).getValue();
var addUrl = "https://maps.googleapis.com/maps/api/geocode/xml?address=" + encodeURIComponent(addr) + "&key=" + apiKey; // or var addUrl = `https://maps.googleapis.com/maps/api/geocode/xml?address=${encodeURIComponent(addr)}&key=${apiKey}`;
var extractXml = UrlFetchApp.fetch(addUrl);
var contentsXml = extractXml.getContentText();
var xmlAdd = XmlService.parse(contentsXml);
var root = xmlAdd.getRootElement();
var ns = root.getNamespace();
var formatted_address = root.getChild("result", ns).getChild("formatted_address", ns).getValue();
return formatted_address;
}
Note:
From your showing image, I guessed that your API key can be used for the API. Please be careful about this.
Reference:
XML Service

Related

Get specific data from Salesbinder API JSON to a Google sheet

Hi what I'm trying to do is get the data from a Salesbinder invoice API (Invoice # is taken from Sheet2 Cell A1) output the data to a Google sheet (sheet Cell A2)
here's the code i'm using to get data from Salesbinder API
function fetching() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Sheet1');
var sheet2 = ss.getSheetByName('Sheet2');
var InvNumber = sheet2.getRange('A1').getValue();
var USERNAME = 'APIKey';
var PASSWORD = 'x';
var url = 'https://mydomain.salesbinder.com/api/2.0/documents.json?documentNumber='+InvNumber+'&contextId=5';
var headers = {
"Authorization": "Basic " + Utilities.base64Encode(USERNAME + ':' + PASSWORD)
};
let response = UrlFetchApp.fetch(url, { headers });
Logger.log(response.getContentText());
}
The result I get from the data is like this
{"document":[{"document_number":8542,"name":"VIOLET BEARINGS","context_id":5,"total_cost":7213.03,"total_tax":415.45,"total_tax2":0,"total_price":8309.08,"total_transactions":0,"issue_date":"2022-12-13T00:00:00+00:00","expiry_date":null,"shipping_address":"(Same as above)","date_sent":null,"shipped_percent":null,"status_id":9,"public_note":"","attention":"GLEN","payment_terms":"","id":"0b24e8d2-e7cc-4441-abd7-337c8a2c6cf1","customer_id":"5b0d881b-6a88-46e5-998c-06330a8e0006","user_id":"5b0d75b3-e9cc-4899-bf99-77610a8e0006","associated_document_id":"","created":"2022-12-13T21:21:43+00:00","modified":"2022-12-21T16:01:04+00:00","status":{"id":9,"name":"unpaid"},"context":{"id":5,"name":"invoice"},"customer":{"id":"5b0d881b-6a88-46e5-998c-06330a8e0006","name":"TestClient","customer_number":1889,"billing_address_1":"Add1","billing_address_2":"Add2","billing_city":"TestCity","billing_region":"TestLoc","billing_postal_code":"TestPost","billing_country":"CANADA","shipping_address_1":"Add1","shipping_address_2":"Add2","shipping_city":"TestCity","shipping_region":"TestLoc","shipping_postal_code":"TestPost","shipping_country":"CANADA"},"user":{"first_name":"test","last_name":"test"},"document_items":[{"id":"0ea11906-f197-4eb4-971e-715d4dc77ab2","name":"B-0832","document_id":"0b24e8d2-e7cc-4441-abd7-337c8a2c6cf1","item_id":null,"unit_id":4,"service_category_id":"5dc2f6d2-27e0-4dac-a2b7-39080a8e0008","description":"JGE/H/K DOOR GASKET","quantity":8,"quantity_partially_received":0,"quantity_partially_shipped":0,"tax":5,"tax2":0,"discount_percent":0,"cost":38.1,"price":43.81,"discounted_price":0,"weight":2,"modified":"2022-12-21T16:01:04+00:00","created":"2022-12-15T15:43:57+00:00","item_variations_location_id":null,"item_variation_data":null,"delete":false,"item":null},{"id":"2b7e9376-ed70-4e75-95c9-6237aad8cfc0","name":"B-0770","document_id":"0b24e8d2-e7cc-4441-abd7-337c8a2c6cf1","item_id":null,"unit_id":4,"service_category_id":"5dc2f6d2-27e0-4dac-a2b7-39080a8e0008","description":"MAIN BEARING JGE/H/K","quantity":8,"quantity_partially_received":0,"quantity_partially_shipped":0,"tax":5,"tax2":0,"discount_percent":0,"cost":282.73,"price":325.14,"discounted_price":0,"weight":6,"modified":"2022-12-21T16:01:04+00:00","created":"2022-12-15T15:43:57+00:00","item_variations_location_id":null,"item_variation_data":null,"delete":false,"item":null},{"id":"391c2ec1-e02d-4390-9a10-429c2ea460fc","name":"B-2082","document_id":"0b24e8d2-e7cc-4441-abd7-337c8a2c6cf1","item_id":null,"unit_id":4,"service_category_id":"5dc2f6d2-27e0-4dac-a2b7-39080a8e0008","description":"ROD BEARING JGE/H/K","quantity":8,"quantity_partially_received":0,"quantity_partially_shipped":0,"tax":5,"tax2":0,"discount_percent":0,"cost":282.73,"price":325.14,"discounted_price":0,"weight":5,"modified":"2022-12-21T16:01:04+00:00","created":"2022-12-15T15:43:57+00:00","item_variations_location_id":null,"item_variation_data":null,"delete":false,"item":null},{"id":"725fb97d-243f-44aa-98d0-50d903871ae4","name":"B-0776","document_id":"0b24e8d2-e7cc-4441-abd7-337c8a2c6cf1","item_id":null,"unit_id":4,"service_category_id":"5dc2f6d2-27e0-4dac-a2b7-39080a8e0008","description":"THRUST PLATE JGE/H/K","quantity":2,"quantity_partially_received":0,"quantity_partially_shipped":0,"tax":5,"tax2":0,"discount_percent":0,"cost":878.44,"price":1010.21,"discounted_price":0,"weight":7,"modified":"2022-12-21T16:01:04+00:00","created":"2022-12-15T15:43:57+00:00","item_variations_location_id":null,"item_variation_data":null,"delete":false,"item":null},{"id":"956cf37e-3921-4cad-94bf-670cdedd2d17","name":"B-1032","document_id":"0b24e8d2-e7cc-4441-abd7-337c8a2c6cf1","item_id":null,"unit_id":4,"service_category_id":"5dc2f6d2-27e0-4dac-a2b7-39080a8e0008","description":"JGK DOOR GASKET","quantity":8,"quantity_partially_received":0,"quantity_partially_shipped":0,"tax":5,"tax2":0,"discount_percent":0,"cost":17.21,"price":19.79,"discounted_price":0,"weight":3,"modified":"2022-12-21T16:01:04+00:00","created":"2022-12-15T15:43:57+00:00","item_variations_location_id":null,"item_variation_data":null,"delete":false,"item":null},{"id":"c2b74c55-465b-4bee-84a1-6c9e8e5bd102","name":"A-0661","document_id":"0b24e8d2-e7cc-4441-abd7-337c8a2c6cf1","item_id":null,"unit_id":4,"service_category_id":"5dc2f6d2-27e0-4dac-a2b7-39080a8e0008","description":"OIL FILTER","quantity":3,"quantity_partially_received":0,"quantity_partially_shipped":0,"tax":5,"tax2":0,"discount_percent":0,"cost":24.5,"price":28.18,"discounted_price":0,"weight":4,"modified":"2022-12-21T16:01:04+00:00","created":"2022-12-15T15:43:57+00:00","item_variations_location_id":null,"item_variation_data":null,"delete":false,"item":null},{"id":"de2c8695-9589-4856-be7a-d3979a776646","name":"FREIGHT CHARGE","document_id":"0b24e8d2-e7cc-4441-abd7-337c8a2c6cf1","item_id":null,"unit_id":1,"service_category_id":"5e6a78e2-cf6c-437c-ac52-0c3a0a8e000a","description":"RE:E2920103","quantity":1,"quantity_partially_received":0,"quantity_partially_shipped":0,"tax":5,"tax2":0,"discount_percent":0,"cost":70.64,"price":95.35,"discounted_price":0,"weight":8,"modified":"2022-12-21T16:01:04+00:00","created":"2022-12-21T15:34:51+00:00","item_variations_location_id":null,"item_variation_data":null,"delete":false,"item":null},{"id":"eab3c271-20ce-4c4e-af92-8553425eaa71","name":"C-6200","document_id":"0b24e8d2-e7cc-4441-abd7-337c8a2c6cf1","item_id":null,"unit_id":4,"service_category_id":"5dc2f6d2-27e0-4dac-a2b7-39080a8e0008","description":"TOP COVER GASKET","quantity":1,"quantity_partially_received":0,"quantity_partially_shipped":0,"tax":5,"tax2":0,"discount_percent":0,"cost":345.85,"price":397.73,"discounted_price":0,"weight":1,"modified":"2022-12-21T16:01:04+00:00","created":"2022-12-13T21:21:43+00:00","item_variations_location_id":null,"item_variation_data":null,"delete":false,"item":null}]}]}
I would like to output the JSON content to look like this on my google sheet
https://docs.google.com/spreadsheets/d/18x1cyztf5SgnKZHjqgWx2iP7SzQAksOzE_EVZoUOcy8/edit#gid=0
Any help would be greatly appreciated
Thanks
In your script, when the value of response.getContentText() is your showing data, how about the following modification?
From:
Logger.log(response.getContentText());
To:
Logger.log(response.getContentText());
// I added the below script.
const value = JSON.parse(response.getContentText());
const addValues = [value.document[0].customer.name, value.document[0].issue_date, value.document[0].attention, value.document[0].name];
const res = value.document[0].document_items.map(o => [value.document[0].document_number, ...["name", "description", "quantity", "price", "discounted_price"].map(h => o[h]), ...addValues]);
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1");
sheet.getRange(sheet.getLastRow() + 1, 1, res.length, res[0].length).setValues(res);
Please set your sheet name to getSheetByName("Sheet1").
Unfortunately, I cannot find External PO# you expect from your data.
Suggestion: Clean and Extract the Required Data
The first thing I noticed is that your current script has already extracted a single JSON object from Salesbinder. However, the format returned is hard to read. You may use JSON beautify sites online to rearrange and analyze the JSON object you have produced like JSON Viewer (DISCLAIMER: I am not affiliated with the website, I just find it helpful to share it to others due to its helpful features).
Script
Afterwards, you may be able to access the data you wanted by accessing the JSON object. For your table, you may use the following:
var salesBinderInvoiceNumVal = object.document[0].document_number;
var itemNameVal = object.document[0].document_items[0].name;
var descriptionVal = object.document[0].document_items[0].description;
var quantityVal = object.document[0].document_items[0].quantity;
var priceVal = object.document[0].document_items[0].price;
var discountPercentVal = object.document[0].document_items[0].discount_percent;
var clientNameVal = object.document[0].customer.name;
var issueDateVal = object.document[0].issue_date;
var attentionVal = object.document[0].attention;
var nameVal = object.document[0].name;
Note: The External PO number can't be found since there are no given values in your sample table.
But before doing so, you may want to store the generated JSON object to a variable. Thus, you may change:
Logger.log(response.getContentText());
to:
var object = response.getContentText();
Afterwards, you may add the setValues() function to add the values to the last row of your table. Thus, your script should be somewhat like this:
function fetching() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Sheet1');
var sheet2 = ss.getSheetByName('Sheet2');
var InvNumber = sheet2.getRange('A1').getValue();
var USERNAME = 'APIKey';
var PASSWORD = 'x';
var url = 'https://mydomain.salesbinder.com/api/2.0/documents.json?documentNumber=' + InvNumber + '&contextId=5';
var headers = {
"Authorization": "Basic " + Utilities.base64Encode(USERNAME + ':' + PASSWORD)
};
let response = UrlFetchApp.fetch(url, { headers });
//Changes start here
var object = response.getContentText(); //Store the JSON object to this variable
var lastRow = sheet.getLastRow(); //get the last row
//required values
var salesBinderInvoiceNumVal = object.document[0].document_number;
var itemNameVal = object.document[0].document_items[0].name;
var descriptionVal = object.document[0].document_items[0].description;
var quantityVal = object.document[0].document_items[0].quantity;
var priceVal = object.document[0].document_items[0].price;
var discountPercentVal = object.document[0].document_items[0].discount_percent;
var clientNameVal = object.document[0].customer.name;
var issueDateVal = object.document[0].issue_date;
var attentionVal = object.document[0].attention;
var nameVal = object.document[0].name;
//add the required values to a 2d array
var output = [[salesBinderInvoiceNumVal, itemNameVal, descriptionVal, quantityVal, priceVal, discountPercentVal, clientNameVal, issueDateVal, attentionVal, nameVal]];
sheet.getRange(lastRow+1, 1, 1, output[0].length).setValues(output); //add the 2d array to the last row of your table
}
Output:
Since your script only gets the value of one cell, I assumed that it only fetches one item or one row to your table. NOTE: I only processed the given JSON object in your post since I have no access/account in Salesbinder. I did assume that it was the output of your current script.
Reference:
You may further study about accessing JSON objects date in this article: JSON Object Literals

How can I properly call a function with a custom UI button with a growing Google Sheet?

I started with this project hoping to be able to have the Google Places API automate the look up of specific business and place information. While I started with individual functions, I see that this created an inordinate amount of requests that blew through my free monthly Google Cloud credit. This was because I wrote functions which were being recalled every time the Sheet was opened in any instance.
Instead, I want to only have the functions run when I use the custom UI button to call it. But how can I specify that it should only run when places do not have their information already populated in the Sheet?
I would have entered the name of a place in Column A, and in Columns B - F, I will have the function find the requested information. My script looks at the name of the place in Column A and finds the Google Place ID. From there, it formats a URL for that Google Places entry and pulls in the requested information from the concatenated URL.
Here is my current code:
// This location basis is used to narrow the search -- e.g. if you were
// building a sheet of bars in NYC, you would want to set it to coordinates
// in NYC.
// You can get this from the url of a Google Maps search.
const LOC_BASIS_LAT_LON = "37.7644856,-122.4472203"; // e.g. "37.7644856,-122.4472203"
function COMBINED2(text) {
var API_KEY = 'AxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxQ';
var baseUrl = 'https://maps.googleapis.com/maps/api/place/findplacefromtext/json';
var queryUrl = baseUrl + '?input=' + text + '&inputtype=textquery&key=' + API_KEY + "&locationbias=point:" + LOC_BASIS_LAT_LON;
var response = UrlFetchApp.fetch(queryUrl);
var json = response.getContentText();
var placeId = JSON.parse(json);
var ID = placeId.candidates[0].place_id;
var fields = 'name,formatted_address,formatted_phone_number,website,url,types,opening_hours';
var baseUrl2 = 'https://maps.googleapis.com/maps/api/place/details/json?placeid=';
var queryUrl2 = baseUrl2 + ID + '&fields=' + fields + '&key='+ API_KEY + "&locationbias=point:" + LOC_BASIS_LAT_LON;
if (ID == '') {
return 'Give me a Google Places URL...';
}
var response2 = UrlFetchApp.fetch(queryUrl2);
var json2 = response2.getContentText();
var place = JSON.parse(json2).result;
var placeAddress = place.formatted_address;
var placePhoneNumber = place.formatted_phone_number;
var placeWebsite = place.website;
var placeURL = place.url;
var weekdays = '';
place.opening_hours.weekday_text.forEach((weekdayText) => {
weekdays += ( weekdayText + '\r\n' );
} );
var data = [ [
place.formatted_address,
place.formatted_phone_number,
place.website,
place.url,
weekdays.trim()
] ];
return data;
}
// add menu
// onOpen is a special function
// runs when your Sheet opens
function onOpen() {
const ui = SpreadsheetApp.getUi();
ui.createMenu("Custom Menu")
.addItem("Get place info","COMBINED2")
.addToUi();
}
I received help on a separate post which advised that I use another function to call COMBINED2 but I am not sure whether that still applies with my change of plans.
// this function calls COMBINED2()
function call_COMBINED2() {
var ss = SpreadsheetApp.getActiveSheet();
var text = ss.getRange("A2").getValue();
var data = COMBINED2(text);
var dest = ss.getRange("B2:F2");
dest.setValues(data);
}
Should it make a difference, my plan for down the road will be to have two buttons in the custom UI. One will work to do the initial lookup of place data. The second will do a refresh. If a change is detected and a cell is changed/updated, then it will highlight in some fashion so that I can make note of this.
The project is part of how I travel. I will often make running Google Sheet lists of recommended and vetted places of interest, bars, and restaurants so that I can import the Sheet into a Google MyMap for reference when we're actually visiting. Over time, these Sheets/MyMaps tend to become obsolete with changes (especially with COVID). I hope this serves to future-proof them and make updating them easier.
The onOpen trigger in your script is only for adding the Custom Menu in your Sheet. The function will only get executed when the user selected an Item in the menu that is associated with the function. In your example, clicking Get place info will execute the COMBINED2 function.
Also, executing the script only when the place information is not present in the sheet is not possible, you have to run the script to get the identifier of the place and compare it to the data in the Sheet. In your example, place.url can be used as identifier. The only thing you can do is to prevent the script from populating the Sheet.
Here I updated your script by changing the function associated to the Get place info to writeToSheet(). writeToSheet() will call COMBINED2(text) to get the place information and use TextFinder to check if the place url exists in the Sheet. If the result of TextFinder is 0, it will populate the Sheet.
// const LOC_BASIS_LAT_LON = "40.74516247433546, -73.98621366765811"; // e.g. "37.7644856,-122.4472203"
const LOC_BASIS_LAT_LON = "37.7644856,-122.4472203";
function COMBINED2(text) {
var API_KEY = 'enter api key here';
var baseUrl = 'https://maps.googleapis.com/maps/api/place/findplacefromtext/json';
var queryUrl = baseUrl + '?input=' + text + '&inputtype=textquery&key=' + API_KEY + "&locationbias=point:" + LOC_BASIS_LAT_LON;
var response = UrlFetchApp.fetch(queryUrl);
var json = response.getContentText();
var placeId = JSON.parse(json);
var ID = placeId.candidates[0].place_id;
var fields = 'name,formatted_address,formatted_phone_number,website,url,types,opening_hours';
var baseUrl2 = 'https://maps.googleapis.com/maps/api/place/details/json?placeid=';
var queryUrl2 = baseUrl2 + ID + '&fields=' + fields + '&key='+ API_KEY + "&locationbias=point:" + LOC_BASIS_LAT_LON;
if (ID == '') {
return 'Give me a Google Places URL...';
}
var response2 = UrlFetchApp.fetch(queryUrl2);
var json2 = response2.getContentText();
var place = JSON.parse(json2).result;
var weekdays = '';
place.opening_hours.weekday_text.forEach((weekdayText) => {
weekdays += ( weekdayText + '\r\n' );
} );
var data = [
place.name,
place.formatted_address,
place.formatted_phone_number,
place.website,
place.url,
weekdays.trim()
];
return data;
}
function getColumnLastRow(range){
var ss = SpreadsheetApp.getActiveSheet();
var inputs = ss.getRange(range).getValues();
return inputs.filter(String).length;
}
function writeToSheet(){
var ss = SpreadsheetApp.getActiveSheet();
var lastRow = getColumnLastRow("A1:A");
var text = ss.getRange("A"+lastRow).getValue();
var data = COMBINED2(text);
var placeCid = data[4];
var findText = ss.createTextFinder(placeCid).findAll();
if(findText.length == 0){
ss.getRange(lastRow,2,1, data.length).setValues([data])
}
}
function onOpen() {
const ui = SpreadsheetApp.getUi();
ui.createMenu("Custom Menu")
.addItem("Get place info","writeToSheet")
.addToUi();
}
Output:
Example 1:
After clicking custom menu:
Example 2:
After clicking custom menu:
Note: Since we are using lastRow, new entry must be inserted below the last row of column A. Otherwise it will overwrite the last entry.
Reference:
Custom Menu
Place Details

How can I make my function run only if there is no fetched data?

I have a Google Apps Script project where I am able to enter the name of a location in Column A of a Google Sheet and have several aspects fetched using the Google Places API. I have been working on this for a bit, and my most recent revision seeks to solve an issue where my function was making too many requests, causing me to burn through the free $200 in credit on the Google Cloud platform.
I noted that every time I opened my Google Sheet, it would have to fetch all of the data again, burning through even more requests just to find information it had already located.
What would be my best bet in order to fetch all of the information and keep it in my Sheet so that it does not make a new request for information every time the Sheet gets opened? Just an If statement to check if there is a value already in one of the cells which gets filled in once the function gets run?
Below is my code. Right now, I just run =COMINED2(A2) in Cell B2 to fetch the information for the place in A2 and that gets placed in B2, C2, D2, E2, etc. Happy to reconfigure things if people have other suggestions?
function COMBINED2(text) {
var API_KEY = 'AIzaSyvxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxQ';
var baseUrl = 'https://maps.googleapis.com/maps/api/place/findplacefromtext/json';
var queryUrl = baseUrl + '?input=' + text + '&inputtype=textquery&key=' + API_KEY + "&locationbias=point:" + LOC_BASIS_LAT_LON;
var response = UrlFetchApp.fetch(queryUrl);
var json = response.getContentText();
var placeId = JSON.parse(json);
var ID = placeId.candidates[0].place_id;
var fields = 'name,geometry,formatted_address,formatted_phone_number,website,url,types,opening_hours';
var baseUrl2 = 'https://maps.googleapis.com/maps/api/place/details/json?placeid=';
var queryUrl2 = baseUrl2 + ID + '&fields=' + fields + '&key='+ API_KEY + "&locationbias=point:" + LOC_BASIS_LAT_LON;
if (ID == '') {
return 'Give me a Google Places URL...';
}
var response2 = UrlFetchApp.fetch(queryUrl2);
var json2 = response2.getContentText();
var place = JSON.parse(json2).result;
var placeName = place.name;
var placeAddress = place.formatted_address;
var placePhoneNumber = place.formatted_phone_number;
var placeWebsite = place.website;
var placeURL = place.url;
var weekdays = '';
place.opening_hours.weekday_text.forEach((weekdayText) => {
weekdays += ( weekdayText + '\r\n' );
} );
var data = [ [
place.name,
place.formatted_address,
place.formatted_phone_number,
place.website,
place.url,
weekdays.trim(),
] ];
return data;
}
I don't think a custom formula will work in your case since custom formulas are supposed to be independent of range, i.e, it should not check a specific cell or range whether it has value or not.
You would be better off creating a regular Apps Script function that calls COMBINED2, and either triggering it manually using the script UI, or probably assigning it to a button.
// this function calls COMBINED2()
function call_COMBINED2() {
var ss = SpreadsheetApp.getActiveSheet();
var text = ss.getRange("A2").getValue();
var data = COMBINED2(text);
var dest = ss.getRange("B2:G2");
dest.setValues(data);
}

Why some of emails which sent by Google Apps Script with Form-submission trigger send twice?

I've noticed my sent email from Google App Script with Form-Submission Triggered and found that 3 in almost 300 of my emails are sent twice!
I have no idea about this and here is what i wrote.
function emailAsExcel(spreadsheetId,subject,message,fileName,link) {
var ss = SpreadsheetApp.openById(spreadsheetId);
var fileId = ss.getId();
var data = ss.getActiveSheet().getRange('A2:D2').getValues();
var url = 'https://docs.google.com/spreadsheets/d/'+fileId+'/export?format=xlsx';
var token = ScriptApp.getOAuthToken();
var response = UrlFetchApp.fetch(url, {
headers: {
'Authorization': 'Bearer ' + token
}
})
var blobs = response.getBlob().setName(fileName+'.xlsx');
var picblobs = DriveApp.getFileById(getIdFromUrl(link)).getBlob().setName(fileName + '.jpg');
var sender = "xxx#gmail.com"+","+ "yyy#gmail.com";
var email = sender;
MailApp.sendEmail(email,subject,message,{attachments: [blobs, picblobs]});
}
function genFile(){
var ss = SpreadsheetApp.openById('xxxxxxxxxxxx');
var sheet = ss.getSheetByName('yyyyy');
var lastRow = sheet.getRange(1,9,sheet.getLastRow()).getValues().filter(String).length;
Logger.log(lastRow);
var date = sheet.getRange('A'+lastRow).getValue();
var name = sheet.getRange('J'+lastRow).getValue();
var surname = sheet.getRange('K'+lastRow).getValue();
var number = sheet.getRange('E'+lastRow).getValue();
var prov = sheet.getRange('F'+lastRow).getValue();
var brand = sheet.getRange('G'+lastRow).getValue();
var link = sheet.getRange('I'+lastRow).getValue();
var subject = name+'_'+surname;
var message =
'customer fullname : '+ name +' '+surname+'\n'
+'num : '+ number + prov+'\n'
+'bra : '+ brand +'\n'
var fileName = name+ ' ' +surname;
Logger.log(link);
var destination = DriveApp.getFileById('aaaaaaaaaaaaaaaa').makeCopy().getId();
var newFile = SpreadsheetApp.openById(destination);
var newSheet = newFile.getSheetByName('bbbbbbbbb');
newSheet.getRange('A2').setValue(date);
newSheet.getRange('B2').setValue(name+' '+surname);
newSheet.getRange('C2').setValue(number);
newSheet.getRange('D2').setValue(prov);
newSheet.getRange('E2').setValue(brand);
newFile.setName(fileName);
emailAsExcel(destination,subject,message,fileName,link);
}
function getIdFromUrl(url) { return url.match(/[-\w]{25,}/); }
genFile();
}
I try to figure it out but I still do not know what happened, so I need you guys' advice.
Thank you in advance!
This likely happens when you (or another Google account that has access to this script) has created multiple triggers.
Go to https://script.google.com/home/triggers, choose your project and remove any duplicate onSubmit triggers.

How to Attach Google Drive File with sendMail Function in Apps Script?

Looking to attach a file (pdfName) from Google Drive to sendMail function in Apps Script. Currently not pulling with the code I have below. Everything else works perfectly. Just having trouble with the attach portion.
function send(formObj) {
var to = formObj.email;
var body = formObj.body;
var sheetName = "POTemplate";
var sourceSpreadsheet = SpreadsheetApp.getActive();
var sourceSheet = sourceSpreadsheet.getSheetByName(sheetName);
var poNo = sourceSheet.getRange("b2").getValue();
var pdfName = "Sample PO Hi Eric " + poNo;
var subject = poNo + " Good morning, Eric";
var attach = DriveApp.getFilesByName(pdfName);
MailApp.sendEmail(to, subject, body, {attachments:[attach]});
}
It looks like, according to the GAS Documentation on sendMail, the attachments argument requires a BlobSource[] (documentation). However getFilesByName() returns a FileIterator. You need to give MailApp any filetype that implements BlobSource.
So to clarify the main issue is that you are trying to give sendMail a list of files (in the form of FileIterator) instead of just one file.
So something like this should work:
function send(formObj) {
var to = formObj.email;
var body = formObj.body;
var sheetName = "POTemplate";
var sourceSpreadsheet = SpreadsheetApp.getActive();
var sourceSheet = sourceSpreadsheet.getSheetByName(sheetName);
var poNo = sourceSheet.getRange("b2").getValue();
var pdfName = "Sample PO Hi Eric " + poNo;
var subject = poNo + " Good morning, Eric";
var listOfFiles = DriveApp.getFilesByName(pdfName); //returns FileIterator of files that match name given.
if(listOfFiles.hasNext()){ //We should check if there is a file in there and die if not.
var file = listOfFiles.next(); //gets first file in list.
MailApp.sendEmail(to, subject, body, {attachments:[file]});
}else{
console.log("Error no file in listOfFiles. Email not sent.");
}
}
edit: I just did some testing and it looks like, for PDFs, the getBlob() is not necessary so I have removed it from my code!