Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
311 views
in Technique[技术] by (71.8m points)

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.

enter image description here

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 + '
' );
  } );

  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.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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.

enter image description here

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 + '
' );
  } );

  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:

enter image description here

After clicking custom menu:

enter image description here

Example 2:

enter image description here

After clicking custom menu:

enter image description here

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:


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

1.4m articles

1.4m replys

5 comments

57.0k users

...