How to wait for an HTTPrequest response - google-apps-script

I have a function that request for an URL and if i use that function with a return in it the entire html code will be in the cell with the function name request "=testFormula()".
Why if i use the formula function it will work but if i use the event one it will not work ?
function onEdit(event){
testCom();
}
// This one will trigger automaticaly when you edit any cell in the sheet
function testCom(){
var doc = SpreadsheetApp.getActiveSpreadsheet();
doc.getRange('a1').setValue("Before fetching");
var response = UrlFetchApp.fetch("http://www.google.com/");
doc.getRange('a1').setValue(response);
}
// Use in a cell "=testFormula()" to run this function
function testFormula(){
var response = UrlFetchApp.fetch("http://www.google.com/");
return response;
}
Thanks in advance

According to the documentation http://goo.gl/khdkL the fetch call is sequential, therefore the next line is not executed until the remote URL has been read
If you look at the example from the documentation, they do exactly what you are tyring to do, show the response in several cells:
var result = UrlFetchApp.fetch("http://api.twitter.com/1/statuses/user_timeline.json",
options);
var o = Utilities.jsonParse(result.getContentText());
var doc = SpreadsheetApp.getActiveSpreadsheet();
var cell = doc.getRange('a1');
var index = 0;
for (var i in o) {
var row = o[i];
var col = 0;
for (var j in row) {
if (fields[j]) {
cell.offset(index, col).setValue(row[j]);
col++;
}
}
index++;
}

The onEdit() is a simple trigger and there are restrictions on what you can do inside simple triggers.
To overcome your problem, do the following
a. Rename your function to something else, say onEdit1()
b. In the script editor, click Resources --> Current project's triggers and add a new on edit trigger pointing to this function.
Authorize your script if needed and you should be good to go

Related

Google App Script Function: Return Drive Filename from File URL

I've searched extensively for an answer to this, but have not had much luck. I think it should be a fairly simple answer. Here's what I'm trying to do:
I have a list of (other) Google Sheets document URLs in a Google Sheet.
I'd like to have a simple function that loads the URL in each line and returns the file name.
To illustrate, if the URLs are in A2:A10, a user could input a custom function like =GetFileName(A2) and it would return the file name corresponding to the Google Sheet URL that is in A2.
Here's the code that I have thus far, but it's returning an error, " TypeError: SpreadsheetApp.openByURL is not a function (line 3)."
function getFileName(fileURL) {
var GetFile = SpreadsheetApp.openByURL(fileURL);
var GetName = GetFile.getName();
return GetName;
}
UPDATE *************
Here's the code that worked for me. Basically, it loops through the sheet with IDs to pull file names. Not the most efficient, but works for relatively short lists just fine.
function getFileName() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
// DocumentIDs are listed in the 'tool' sheet
var targetSheet = ss.getSheetByName("tool");
// Find the last row
var LastRow = targetSheet.getLastRow();
//Loop Begins
//Initializing variable assumes that the first valid Drive ID is in the 3rd row.
for(var i = 3; i < LastRow; i++) {
// Get the Document ID. This gets the Document ID
var LookupID = targetSheet.getRange(i,1).getValue();
// Get the Filename
var FileOpen = SpreadsheetApp.open(DriveApp.getFileById(LookupID));
var FileName = FileOpen.getName();
// Write the FileName in the second column
targetSheet.getRange(i,2).setValue(FileName);
Logger.log(i);
Logger.log(LookupID);
Logger.log(FileName);
}
}
Suggestion:
You can try this script together with using clickable image to act as a button for the function to run the script below:
function convertURLtoSheetName() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var cell = ss.getActiveCell().getValue();
var getSheetName = SpreadsheetApp.openByUrl(cell).getName();
var seturl = SpreadsheetApp.newRichTextValue().setText(getSheetName).setLinkUrl(cell).build();
ss.getActiveCell().setRichTextValue(seturl);
}
Here is how you assign a script function to an image:
Custom Functions Cannot open other spreadsheets (SpreadsheetApp.openById() or SpreadsheetApp.openByUrl()).
reference

Absolute reference with UDF & filtering data in google sheets script

Hi I am begineer to scripts, here is the code and googlesheet for reference
What i am getting
what i want to achieve
/**
*#param quest1 Question of the note
*#param quest1 Answer of the note
*#customfunction*/
function DMNOTE(quest1,ans1,quest2,ans2,quest3,ans3,) {
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var result = quest1+'-'+ans1+','+quest2+'-'+ans2+','+quest3+'-'+ans3;
return result;
}
I want to achieve absolute reference for "quest" parameter and i want it to loop for rest of the coulmns till column where i enter the function.Also under "Formula Required" column i have put formla for reference thats how i want my UDF to work.
Follwing up i need to filter "Non-solicit agreement" and keep only "No" under it and Copy & Paste all colums highlited in blue to update tab.
function toFilter (){
// filter and retain "no" in non-solicit agreement
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Worksheet");
ss.getRange(1,1,ss.getLastRow(),ss.getLastColumn());
var createfilter = SpreadsheetApp.newFilterCriteria().setHiddenValues("Yes").build();
ss.getFilter().setColumnFilterCriteria(8, createfilter);
}
Hope i make sense. Any help is appriciated
To work with any number of arguments, you can have a function like below in your script
function DMNOTE(...arg){
let result = ''
for(let i=0;i<arg.length;i++){
result += `${arg[i]}-${arg[i+1]},`
i++;
}
return result.substring(0, result.length - 1);
}
And then form your spreadsheet you can call as =DMNOTE(A$1,A2,B$1,B2,C$1,C2) or =DMNOTE(A$1,A2,B$1,B2,C$1,C2,D$1,D2) The function will process all the arguments being passed and returns the result.
How to filter "non-solicit agreement" and take only data which has "no"
The code you provided comes already very close to what you desire, you just need to make the following adjustments:
setHiddenValues() expect an array, no a string, so change "Yes" to ["Yes"]
getFilter() only works to modify a filter that is present in the sheet already, in case there is none, it is better to delete the old filter and create a new one:
function toFilter(){
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Worksheet");
ss.getRange(1,1,ss.getLastRow(),ss.getLastColumn());
var createfilter = SpreadsheetApp.newFilterCriteria().setHiddenValues(["Yes"]).build();
if(ss.getFilter() != null) {
ss.getFilter().remove();
}
ss.getDataRange().createFilter().setColumnFilterCriteria(8, createfilter);
}
How to Copy & Paste all columns highlited in blue to update tab
Build a function that
calls toFilter()
checks for all rows either they are hidden
copies the non- hidden rows into an array
pastes this array to the sheet Update
function copyIfNotHidden(){
toFilter();
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var sheet1 = spreadsheet.getSheetByName("Worksheet");
var sheet2 = spreadsheet.getSheetByName("Update");
var data = sheet1.getDataRange().getValues();
var array = [];
for (var i = 0; i < data.length; i++){
if(sheet1.isRowHiddenByFilter(i+1)==false){
array.push(data[i]);
}
}
sheet2.getRange(sheet2.getLastRow()+1, 1, array.length, array[0].length).setValues(array);
}

Share drive folder to list of user from google spreadsheet

I'm trying to write a script to take a folder from my drive and share it with a list of users.
The script is scheduled to run on a sheet which collects answers from a google form and I set the trigger to run the script at every new submission of the form.
First Attempt. I think there is something wrong with how I create the array containing the list of emails, but I couldn't figure out how to fix it.
function driveShare_array() {
var getEmails =[];
var sheet= SpreadsheetApp.getActiveSheet();
var getEmails = sheet.getRange('emailstoshare').getValues(); // emailtoshare is a named range including only the one column where the sheet collects emails from the form submission
var folder = DriveApp.getFolderById('xxxx'); // added the folder ID in here
for ( i in getEmails) {
folder.addViewer(getEmails[i])
}
}
Second attempt to try to control how the array "getEmails" gets shaped by adding elements (push) one by one
function driveShare_push(){
var getEmails =[];
var sheet= SpreadsheetApp.getActiveSheet();
var range = sheet.getRange('emailstoshare'); // emailtoshare is a named range including only the one column where the sheet collects emails from the form submission
for (var i=0;i<range.getNumRows();i++){
getEmails.push(range.offset(i,0,1,1).getValue());
}
var folder = DriveApp.getFolderById('xxxx'); // added the folder ID in here
// for ( var j=0;j<range.getNumColumns();j++) {
for (var j in getEmails){
folder.addViewer(getEmails[j]) ;
}
}
Both versions (and other variations I have tested) give errors. Could someone help me understand what I'm doing wrong and how to fix it?
Thanks!!
It might be possible that the email address may not have a Google account and thus the folder could not be shared with them. You might consider adding a try/catch block to ignore such errors.
function driveShare_array() {
var getEmails =[];
var sheet= SpreadsheetApp.getActiveSheet();
var folder = DriveApp.getFolderById('xxxx');
var getEmails = sheet.getRange('emailstoshare').getValues();
getEmails.forEach(function(email) {
if (/\S+\.\S+/.test(email)) {
try {
folder.addViewer(email)
} catch (f) {
Logger.log("Cannot share with " + email);
}
}
})
}
If your triggering the function with onFormSubmit trigger then you can do something like this.
function driveShare_array(e) {
var folder=DriveApp.getFolderById('xxxx');
folder.addViewer(e.namedValues['Email Address']);
}
You will probably want to store them on some spreadsheet and retrieve them to make sure you haven’t already added it to the list. You will need to create an onFormSubmit trigger for the function in script editor Edit/Project Triggers.
Used logger.log to understand the structure of the data and the code was creating a matrix instead of an array.
Here the corrected code for reference:
function driveShare_push(){
var getEmails =[];
var sheet= SpreadsheetApp.getActiveSheet();
var range = sheet.getRange('emailstoshare');
for (var i=1;i<range.getNumRows();i++){
if(range.offset(i, 0, 1, 1).getValue().indexOf("gmail.com") > -1){
getEmails.push(range.offset(i,0,1,1).getValue());
}
}
var folder = DriveApp.getFolderById('xxxx');
folder.addViewer(getEmails[getEmails.length-1]) ;
}

Google Apps Script - How do I create a trigger for a function with a parameter? [duplicate]

This question already has answers here:
Refresh data retrieved by a custom function in Google Sheet
(21 answers)
Closed 5 years ago.
I am working on a tracker for my cryptocurrencies. In the following function I fetch data from an API and put in a cell. For instance, when I want to get the value of Bitcoin in USD I put the formula =getCryptodata("bitcoin", "price_usd") in a cell and it returns the current price.
function getCryptoData(coin, api) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var url = 'https://api.coinmarketcap.com/v1/ticker/' + coin + '/';
var response = UrlFetchApp.fetch(url, {'muteHttpExceptions': true});
var json = response.getContentText();
var data = JSON.parse(json);
return parseFloat(data[0][api]);
}
I want the price to be updated every minute, so I've set a trigger for the function. All of a sudden there seems to be a problem now. Every time the function is triggered this error shows up (in my mailbox).
TypeError: Cannot read property "(class)#35c554a5" from undefined.
When I run the script from the script editor I get the same problem. Then I came up with the idea to check whether the parameters in the function are defined or not, and if not, to give them a generic value. Google seemed to have a problem with the undefined parameters (although they are defined in the sheet). So I added these lines before the lines I already had in my function.
if (typeof coin == 'undefined') {
var coin = "bitcoin";
}
if (typeof api == 'undefined') {
var api = "price_usd";
}
else {
Now the error is gone but the values are not updated when the function is triggered.
What am I missing?
So there are a few things going on here:
You are trying to load an active spreadsheet but when you trigger a script, there is no active spreadsheet. (Why are you loading this spreadsheet? You don't seem to be doing anything with it)
Your script returns a value but if your script is triggered from a schedule (so not from another function) the returned value will be returned to nothing. You need to put this value somewhere useful.
A solution to this issue could be to directly put the value from the API in the sheet. To do this the script requires a small change.
Example:
function getCryptoData(coin, api, target) {
var ss = SpreadsheetApp.openById("YOUR-ID-HERE"); // The script has no active spreadsheet so make sure to acquire the spreadsheet by providing the ID
var sheet = ss.getSheets()[0];
var url = 'https://api.coinmarketcap.com/v1/ticker/' + coin + '/';
var response = UrlFetchApp.fetch(url, {'muteHttpExceptions': true});
var json = response.getContentText();
var data = JSON.parse(json);
SpreadsheetApp.getRange(target).setValue(parseFloat(data[0][api])); // Write the returned value directly to the range (e.g. A1) that was specified as an argument
}
So with such a function, you may create another function that you would trigger every one in a while to update your values:
function updateCryptoData() {
getCryptodata("bitcoin", "price_usd", "B2");
getCryptodata("bitcoin", "price_usd", "C2");
}
You don't need to put a formula in your sheet anymore if you choose to go with a solution like this one.
Note: this solution is not optimal from a performance point of view. However, I've tried to make as little code changes to your own code to make it work. Ideally you'd only call your API once per coin type.
When using fetch you should be aware there are daily limits to how much data can come back from the call. Because you are calling for data every minute, it is well within the realms of possibility you will/have exceeded the daily limit.
This will not need to reload the spreadsheet. First you have to create the trigger for specific coin and pricing.
function teste()
{
trigger_("bitcoin","price_usd","B2");
trigger_("litecoin","price_usd","B3");
trigger_("bitcoin","price_usd","B4");
}
Store the data corresponding to the trigger, with reference to the trigger_id.
function trigger_(coin,api,target)
{
var new_trigger = ScriptApp.newTrigger(getCryptoData).timeBased().everyMinutes(1).create();
var trigger_id = new_trigger.getUniqueId();
PropertiesService.getUserProperties().setProperty(trigger_id, coin+","+api+","+target);
}
When the trigger gets fired, it gets the corresponding data(coin,price,target) with which the api is called and the target cell gets updated.
function getCryptoData(event)
{
var trig = ScriptApp.getProjectTriggers();
for(var i =0; i<trig.length; i++)
{
if(trig[i].getUniqueId()== event.triggerUid )
{
var cryptoData = PropertiesService.getUserProperties().getProperty(event.triggerUid);
cryptoData = cryptoData.split(",");
var coin = cryptoData[0];
var api = cryptoData[1];
var target = cryptoData[2];
var ss = SpreadsheetApp.openById("YOUR_SPEADSHEET_ID").getSheets(); // change accordingly
var sheet = ss[0]; // change accordingly
var url = 'https://api.coinmarketcap.com/v1/ticker/' + coin + '/';
var response = UrlFetchApp.fetch(url, {'muteHttpExceptions': true});
var json = response.getContentText();
var data = JSON.parse(json);
Logger.log(parseFloat(data[0][api]));
ss[0].getRange(target).setValue(parseFloat(data[0][api]));
}
}
}
NOTE : Daily limit to run trigger for normal user is 90min/day
app script quota

Using Built-in function inside Custom Function

In google sheets, I'd like my custom function to use one of the built-in functions. Specifically, this function would take a string as a parameter, comb through another sheet to locate that value, and then return a link to that cell's address.
function href(findMe) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var hrefSheet = ss.getSheetByName("otherSheet");
hrefSheet.activate();
var rows = hrefSheet.getDataRange();
var numRows = rows.getNumRows();
var finds = rows.getValues()[1][0];
var hrefCellRow;
for(var i=0; i<numRows; i++){
if(finds[i][0] == findMe){
hrefCellRow = i+1;
break;
}
}
return address(hrefCellRow, 1); //address() is a function that is built in to sheets
}
So if I have a value "XYZ" in the "otherSheet" sheet, when I type
=href("XYZ")
I'd like it to try to find the value XYZ and return an address to the active cell. Extra kudos if it returns an actual link that when clicked, goes to that cell.
I didn't think of this before. I could just use the address function outside of the script and have the custom function embedded in the built-in function (instead of the reverse, which I tried to do). it's not as pretty, but it would be simply
=address(href(findMe),1)
Still haven't found a way to link to another cell, without perhaps creating a function that would reset the active cell?