Google Docs Custom Refresh Script - google-apps-script

I'm trying to write a script that allows me to execute commands as soon as Google finishes making calculations (i.e. I'm trying to add to a script to Google docs that imitates some VBA "Calculate" functionalities).
The script is conceived to work by converting a range into a string and looking for the substring "Loading..." (or "#VALUE!" or "#N/A") in that string. The "while" loop is supposed to sleep until the unwanted substrings are no longer found in the string.
I'm using the following spreadsheet as a sandbox, and the code seems to work okay in the sandbox just searching for "Loading...":
https://docs.google.com/spreadsheet/ccc?key=0AkK50_KKCI_pdHJvQXdnTmpiOWM4Rk5PV2k5OUNudVE#gid=0
In other contexts, however, I have cells whose values may return as "#VALUE!" or "#N/A" for reasons other than the fact that Google is still loading/thinking/calculating. What's the way around this?
function onEdit() {
Refresh();
};
function Refresh () {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1");
var sheet2 = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet2");
// set the range wherever you want to make sure loading is done
var range = sheet.getRange('A:A')
var values = range.getValues();
var string = values.toString();
var loading = "Loading";
do
{
var randomWait = Math.floor(Math.random()*100+50);
Utilities.sleep(randomWait);
}
while (string.search(loading) ==! null);
range.copyTo(sheet2.getRange('A1'), {contentsOnly:true});
customMsgBox();
};
function customMsgBox() {
Browser.msgBox("Data refreshed.");
};

rather than using a while loop to "sleep" you should add an event handler to your document which captures the update/refresh event and then runs whatever math/processing you need.
Here's a good place to start reading about events: https://developers.google.com/apps-script/understanding_events
but if you search the api documents for eventhandler you can get some example code fast...

Related

Google Script Function - Copy Paste

I was writing a script through Google Script about the function of a button when clicked. What I want to happen is SHEET 1 Values gets copied to SHEET 2 AS VALUES (Not copying the Google Sheets Formulas), then SHEET 1 VALUES will get cleared. However, it seems I'm having an issue with the values getting copied to SHEET 2.
I tried to search for something that could resolve this, but I'm not really that an expert when it comes to writing scripts since I'm a newbie to this.
// Display a dialog box with a message and "Yes" and "No" buttons.
var ui = SpreadsheetApp.getUi();
var response = ui.alert("Do you want to capture all data?", ui.ButtonSet.YES_NO);
// Process the user's response.
if (response == ui.Button.YES) {
}
function remove() {
var spreadsheet = SpreadsheetApp.getActive().getSheetByName("2019")
var destsheet = SpreadsheetApp.getActive().getSheetByName("Handled Tickets");
var getLastContentRow = spreadsheet.getRange("A8:I").getValues();
var destination = destsheet.getRange(destsheet.getLastRow()+1,1);
var source = spreadsheet.getRange("A8:I").getValues();
getLastContentRow.copyTo(destination.CopyPastType.PASTE_VALUES);
spreadsheet.getRange('C8:E').clearContent()
spreadsheet.getRange('F8:H').clearContent()
}
Expected Flow: 1) When the button has been clicked, whatever data in spreadsheet will be copied to destsheet. 2) Once copied, data in spreadsheet will be cleared.
Additional rules: 1) Once copied to destsheet, data will not be overwritten by other values when the button is clicked again. Instead, it will look for the last row (empty cell) and copy the data there. 2) If all cells have been used, automatically there will be additional 100 rows added.
Error:
Cannot find function copyTo in object
There are several issues with your code above (syntax, format, structure, missing semicolons to finish statements,...).
Assuming only the remove() function was being a problem, here is my version below with several comments.
You may also want to review the part with the UI above (e.g. embed it in a function that your button will call, make sure there is some code in your if statement,...).
function remove() {
var source_sheet = SpreadsheetApp.getActive().getSheetByName("2019"); // better not use "spreadsheet" as variable name here, this is confusing, your content is a sheet
var dest_sheet = SpreadsheetApp.getActive().getSheetByName("Handled Tickets");
var getLastContentRow = source_sheet.getRange("A8:I"); // The "copyTo" method applies to ranges, not to arrays, so remove the ".getValues()"
// --> the "getLastRow" variable name makes me believe you're only looking at copying the last row, but your current range will copy all rows starting at 8.
// --> as the same content is captured in "source" below, this might just be a misleading variable name, though, in which case you may want to simply rename it
var destination = dest_sheet.getRange(dest_sheet.getLastRow()+1,1);
// var source = spreadsheet.getRange("A8:I").getValues();
// --> this is duplicate with getLastContentRow, and not use in your function, so presumed useless. Can be removed.
getLastContentRow.copyTo(destination, SpreadsheetApp.CopyPasteType.PASTE_VALUES, false);
// --> the example in the documentation is misleading, but this function requires a third argument for "transposed"
// spreadsheet.getRange('C8:E').clearContent()
// spreadsheet.getRange('F8:H').clearContent()
// --> why two different calls instead of 1 on C8:H directly?
// --> also, why not the same range as the one copied?
getLastContentRow.clearContent(); // This will remove all the copied content from the "source_sheet"
}

Writing into another Spreadsheet with SetValue fails without errors/exceptions

I'm trying to update a spreadsheet from a script running on another spreadsheet.
Nothing seems to have any effect on the table (SetValue(), SetBackgroundRGB(), etc.).
I've checked the scope, it includes "https://www.googleapis.com/auth/spreadsheets" permission; besides, this same script has no problem writing to another spreadsheet that it creates in runtime.
function updateAnotheSpreadsheet() {
var targetSpreadsheet = SpreadsheetApp.openById('<target spreadsheet id>');
var sheet = targetSpreadsheet.getSheetByName('<sheet name>');
Browser.msgBox(sheet.getRange("A1").getValue()); // Here I see that my getSheetByName worked
sheet.getRange("A1").setValue('Test value'); // But this does nothing
}
There are no errors but also no effect: nothing changes in the target spreadsheet.
Hi I was testing and was able to do what you are trying to do. You can try to declare a variable for the message, here is what I was able to do, I hope this resolves your inquiry.
function updateAnotherSpreadsheet() {
//Opening the second Spreadsheet by ID
var targetSpreadSheet = SpreadsheetApp.openById("<SpreadSheetId>");
var sheet = targetSpreadSheet.getSheetByName("<SheetName>");
//Getting the range to show on msgBox.
var msg = sheet.getRange("A1:A1").getValue();
//Displaying old data on A1:A1 from the secondary Spreadsheet.
Browser.msgBox("Old data on secondary spreadsheet: " + msg);
//Getting the range and setting new values
sheet.getRange("A1:A1").setValue('Test value').setBackground("teal").setFontColor("white");
}
I would suggest to check for more information here https://developers.google.com/apps-script/guides/sheets.
I hope this helps, greetings.
I found the problem.
A function called from OnEdit() can't ask for permissions. I needed to first update that other spreadsheet from any function called by something "active", like a button; then, after the script asks for permission once, it can do its thing from OnEdit() the next time it runs.

How to trigger Google Apps script function based on insert row via api

I have a Google Sheet with 5 columns (First Name, Address, SKU, Quote, Status).
I have an apps script function (createQuote) which looks at the above variable's values from google sheet row and create a google document quote replacing the variables to values.
I use Zapier to insert row into my above google sheet.
What am struggling with-:
I need a way to trigger my createQuote function right when a new row is inserted via zapier (Google Sheet API call).
I tried playing with triggers but couldn't make it, any help is appreciated.
thank you
here is the code for my function-
function quoteCreator(){
docTemplate = "googledocidgoeshere"
docName = "Proposal"
var sheet = SpreadsheetApp.getActive().getSheetByName("Main")
var values = sheet.getDataRange().getValues()
var full_name = values[1][0]
var copyId = DriveApp.getFileById(docTemplate).makeCopy(docName+" for "+full_name).getId()
// Open the temporary document
var copyDoc = DocumentApp.openById(copyId);
// Get the document’s body section
var copyBody = copyDoc.getActiveSection();
// Replace place holder keys/tags,
copyBody.replaceText("keyFullName", full_name);
copyDoc.saveAndClose();
// Convert temporary document to PDF by using the getAs blob conversion
var pdf = DriveApp.getFileById(copyId).getAs("application/pdf");
// put the link of created quote in the quote column
var url = DocumentApp.openById(copyId).getUrl()
var last = sheet.getRange(2, 7, 1, 1).setValue(url)
}
Note-: I haven't put the loop yet in above, i'll do that once it starts working as per my requirements.
Changes made via Sheets API or Apps Script do not fire onEdit triggers. I give two workarounds for this.
Web app
Have whatever process updates the sheet also send a GET or POST request to your script, deployed as a web application. As an example, a GET version might access https://script.google.com/.../exec?run=quoteCreator
function doGet(e) {
if (e.parameter.run == "quoteCreator") {
quoteCreator();
return ContentService.createTextOutput("Quote updated");
}
else {
return ContentService.createTextOutput("Unrecognized command");
}
}
The web application should be published in a way that makes it possible for your other process to do the above; usually this means "everyone, even anonymous". If security is an issue, adding a token parameter may help, e.g., the URL would have &token=myToken where myToken is a string that the webapp will check using e.parameter.token.
GET method is used for illustration here, you may find that POST makes more sense for this operation.
Important: when execution is triggered by a GET or POST request, the methods getActive... are not available. You'll need to open any spreadsheets you need using their Id or URL (see openById, openByUrl).
Timed trigger
Have a function running on time intervals (say, every 5 minutes) that checks the number of rows in the sheet and fires quoteCreator if needed. The function checkNewRows stores the number of nonempty rows in Script Properties, so changes can be detected.
function checkNewRows() {
var sp = PropertiesService.getScriptProperties();
var oldRows = sp.getProperty("rows") || 0;
var newRows = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Main").getLastRow();
if (newRows > oldRows) {
sp.setProperty("rows", newRows);
quoteCreator();
}
}

Google sheet not updating custom function return value

I am very new to Google Apps Script (as well as JavaScript, for that matter), but I have been trying to tinker with it for fun.
I have tried writing a script to fetch API price data in Google Sheets, but am finding that the returned value is not updating when re-evaluating the script in the same cell.
Below is a script to fetch bitcoin price data from Coinbase's API. The script parses the JSON response of the request, as is described here.
function getBTCPrice() {
var url = "https://api.coinbase.com/v2/prices/BTC-USD/spot";
var response = UrlFetchApp.fetch(url);
var jsonSpotPrice = response.getContentText();
var parseSpotPrice = JSON.parse(jsonSpotPrice);
var price = "$" + parseSpotPrice.data.amount;
return price
}
Now, if I type =getBTCPrice() in some cell, and then re-evaluate a few moments later, I get the same price; however, if I evaluate the script in a different cell, I get a different result.
I've read some stuff about Google caching values in cells, so that perhaps the script isn't evaluated because the value of the cell has not changed. Is this the case here? If so, is there a workaround?
Any help is greatly appreciated!
I finally figured it out! Instead of trying to call the custom function from an actual sheet cell (which apparently stores cached values), the trick is to call the function within a script.
Using my above script:
function getBTCPrice(url) {
var response = UrlFetchApp.fetch(url);
var jsonSpotPrice = response.getContentText();
var parseSpotPrice = JSON.parse(jsonSpotPrice);
var price = "$" + parseSpotPrice.data.amount;
return price;
}
You can then call this function from another script. Specifically, I was looking to assign the updated price to a cell. Below is an example, which assigns the price to the active spreadsheet, in cell A1:
function updatePrice(){
var a = getBTCPrice("https://api.coinbase.com/v2/prices/BTC-USD/spot");
SpreadsheetApp.getActiveSpreadsheet().getRange('A1').setValue(a);
}
You can then proceed to set an appropriate time trigger. And that's all there is to it!
Have a look at this answer on Refresh data retrieved by a custom function in google spreadsheet.
As the answerer says, the trick is to
My solution was to add another parameter to my script, which I don't even use. Now, when you call the function with a parameter that is different than previous calls, it will have to rerun the script because the result for these parameters will not be in the cache.
Vik
In addition of Vikramaditya Gaonkar answer, you can use a installable trigger to get a refresh result each minute.
function getBTCPrice(input) {
url = "https://api.coinbase.com/v2/prices/BTC-USD/spot";
response = UrlFetchApp.fetch(url);
var jsonSpotPrice = response.getContentText();
var parseSpotPrice = JSON.parse(jsonSpotPrice);
var price = "$" + parseSpotPrice.data.amount;
return price
}
function up(){
SpreadsheetApp.getActiveSheet().getRange('A1').setValue(Math.random());
}
The parameter of getBTCPrice function is, in my case, cell A1 which is randomize each minute. For this, I create a installable trigger on up function
function up, time-driven, minute timer, every minute
I was also trying to make my custom function update, after searching I came up with the following function:
function updateFormulas() {
range = SpreadsheetApp.getActiveSpreadsheet().getDataRange();
formulas = range.getFormulas();
range.clear();
SpreadsheetApp.flush();
range.setValues(formulas);
}
The function above update all formulas of the spreadsheet. In my experience to make a custom function update I had to change its value, so I get all the data of the sheet, then I get the formulas and store them into a variable, then I clear their values and apply this change with "flush", finally I update the values I have just cleared with the formulas I have stored.
I created this function and in my case I have set the trigger for 1 minute to execute it, every minute all functions of the table are updated.
I hope this helps you.

How do I hide rows in a Google spreadsheet using a script based on a character in a column?

I'm using the below script to hide all rows with a value of "1" in column B.
function myfunction () {
function onOpen() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("CSSMaster");
var maxRows = sheet.getMaxRows();
//show all the rows
sheet.showRows(1, maxRows);
//get data from clumn B
var data = sheet.getRange('B:B').getValues();
//iterate over all rows
for(var i=0; i< data.length; i++){
//compare first character, if 1, then hide row
if(data[i][0].charAt(0) == '1'){
sheet.hideRows(i+1);
}
}
}
}
I go to my spreadsheet and enter the following: =myfunction(B:B) , I see "thinking" but nothing happens. What am I doing wrong? (I'm VERY new to Google scripts and am guessing this is a very basic error.)
Your instincts are right - it's a very basic error. First, make sure that you're actually running the code you intend to.
In this example, you've got a function inside a function. When you invoke myFunction() from the spreadsheet, execution will begin but won't find any code to run. Because onOpen() is enclosed in myFunction(), but there is no call in myFunction() to onOpen(), there's nothing to do.
Sort that out - you probably just need to get rid of the declaration and closing brace for onOpen().
Next, you're passing a 2-dimensional array of values when you invoke myFunction(B:B), but you have no parameter handing in the function. (You retrieve the values explicitly with getRange('B:B').getValues().)
General advice: learn some JavaScript (I recommend CodeAcademy), and try your hand at some of the Google Apps Script tutorials. With the competence and confidence from those, you should have no problem getting this working on your own.