I'm new to Google Sheets and Apps Script. I have a sheet with two URLS. In cell F1 is http://testurl1.com and in cell G1 is http://testurl2.com.
I would like to have a button or link or something in cell D1 that when I click it will open both of these links. I can do this manually with Alt-Enter but haven't been able to translate that to code.
I have been able to open both these urls from a menu item, but when I try calling the code from a cell it says
Exception: Cannot call SpreadsheetApp.getUi() from this context.
But the code works from a menu item. Weird. The code I'm currently trying to use is below but I am open to any suggestions!
function callOthers() {
myFunction()
Utilities.sleep(1500);
myFunction2()
}
function myFunction() {
var sheet = SpreadsheetApp.getActiveSheet();
var selection = sheet.getRange("F1").getValue();
var html = "<script>window.open('" + selection + "');google.script.host.close();</script>";
var userInterface = HtmlService.createHtmlOutput(html);
SpreadsheetApp.getUi().showModalDialog(userInterface, 'Open Tab');
}
function myFunction2() {
var sheet = SpreadsheetApp.getActiveSheet();
var selection2 = sheet.getRange("G1").getValue();
var html2 = "<script>window.open('" + selection2 + "');google.script.host.close();</script>";
var userInterface2 = HtmlService.createHtmlOutput(html2);
SpreadsheetApp.getUi().showModalDialog(userInterface2, 'Open Tab');
}
Problem
Custom function is blocked from being run when used in a cell
Explanation
There are three main ways of making a bound script function available in the Spreadsheet UI:
As a custom function that can be used like a formula
As a menu item that will run the function on click
As a "button" created via image or drawing that will run the function on click
All three have different execution context and limitations on what they can and cannot access, the most restrictive being the first. Custom functions execution context is bound to the cell it is called in, so you cannot do anything that affects the UI as a whole, which getUi() allows to do.
Additionally, since showModalDialog is a method that requires authorization on behalf of the user, even if the getUi() method was available, you could not show the dialog due to the fact that custom functions never ask users to authorize access to personal data.
Solution
If you want to interact with UI, you should either create a menu or a button as mentioned before.
Please note that users will have to give your script their permission for the following scope:
https://www.googleapis.com/auth/script.container.ui
References
Custom functions guide
showModalDialog method reference
getUi() method reference
Related
I'm new to apps script.
I'm developing a ticketing system that, from a form responses, automatically creates new spreadsheets with the right ticket on it.
It works like a charm but I'm now having a problem...
I just want to know why, even if I activate the sheet opened by id, the function getCurrentCell() always return me the cell A1,
even if I have a different cell selected.
This is the simple function:
function getValue() {
var sheet = SpreadsheetApp.openById(id="").getSheetByName("Open Tickets").activate();
var cell = sheet.getCurrentCell().getValue().toString();
Logger.log(cell)
}
Thanks
I have a problem with some Google Script stuff. Basically, my goal is to have the script check to see if a client's case was resolved and then send an email to them that the issue has been resolved. I've gotten the logic done on when to send an email, but every time I try and implement it into the spreadsheet, I get the error:
Error
You do not have permission to call MailApp.sendEmail. Required permissions: https://www.googleapis.com/auth/script.send_mail (line 8).
I've got a simple function to test the functionality of it, and when run in the script editor it works fine, but not on the spreadsheet. Here is my sample function:
function myFunction(row) {
var sheet = SpreadsheetApp.getActiveSheet();
var rng = sheet.getRange(row, 1, 1, 2);
var ara = rng.getValues();
var email = ara[0][0];
MailApp.sendEmail(email, "TEST", "This is a test of sendEmail().");
return "Email sent.";}
According to the Apps Script Custom Functions documentation:
If your custom function throws the error message You do not have permission to call X service., the service requires user authorization and thus cannot be used in a custom function.
To use a service other than those listed above, create a custom menu that runs an Apps Script function instead of writing a custom function. A function that is triggered from a menu will ask the user for authorization if necessary and can consequently use all Apps Script services.
Method 1
Basically, you can replicate the wanted behavior of the two functions above with this:
function SendEmail() {
var message = "This is your response";
var subject = "You have feed back in the parking lot";
var ss = SpreadsheetApp.getActiveSheet();
var textrange = ss.getRange("F2");
var emailAddress = ss.getRange("B2").getValue();
if (textrange.isBlank() == false)
MailApp.sendEmail(emailAddress, subject, message);
}
And in order to trigger the execution of this function, you can make use of Apps Script triggers and choose one which is the most convenient for your use-case.
Method 2
You can also create a custom menu and with the option of triggering the above function. You only need to add this:
function onOpen() {
var ui = SpreadsheetApp.getUi();
ui.createMenu("My Menu")
.addItem("Send Email", "SendEmail")
.addToUi();
}
And this is how it will look like on the Spreadsheet:
Reference
Apps Script Custom Functions;
Apps Script Range Class - isBlank();
Apps Script Custom Menus;
Apps Script Triggers.
I encountered the same problem today "You do not have permission to call MailApp.sendEmail".
I solved this by doing the next steps:
open "Tools" -> "Script editor"
in "Script editor" click on "View" -> "Show manifest file"
open the "appscript.json" file that appeared in the left section of your screen and add "https://www.googleapis.com/auth/script.send_mail" to the oauthScopes, like this:
{
"oauthScopes": ["https://www.googleapis.com/auth/spreadsheets", "https://www.googleapis.com/auth/script.send_mail"],
}
PS: I assigned the script to an image, which basically acts like a button.
I'm using the 'Cryptofinance' custom function in Google spreadsheets. I have written a custom script which runs daily using the trigger functionality of the app scripts.
function daily() {
SpreadsheetApp.flush();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getSheetByName("Liquidity");
var value = sh.getRange("B36").getValue();
var lastRow = whichRow();
lastRow += 1;
ss.getSheetByName("Liquidity over time").getRange("B" + lastRow).setValue(value);
ss.getSheetByName("Liquidity over time").getRange("A" + lastRow).setValue(new Date());
}
function whichRow() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var Avals = ss.getSheetByName("Liquidity over time").getRange("A1:A").getValues();
var Alast = Avals.filter(String).length;
return Alast;
}
Basically it should pick up the value from a field and add a row so I can run a chart on this column.
The output when I run it manually from the script editor can look like this;
1337999,52
The output when it gets run by the daily function looks like this:
#NAME?
It didn't help with the .flush() function and I haven't been able to understand the sheets lifecycle in combination with the custom function.
How can I make sure the sheet is pre-loaded before the script runs?
You need to open the Spreadsheet and then set it as active:
var ss = SpreadsheetApp.openById("1234567890");
SpreadsheetApp.setActiveSpreadsheet(ss);
https://developers.google.com/apps-script/reference/spreadsheet/spreadsheet-app#setActiveSpreadsheet(Spreadsheet)
Short answer
On time-driven triggers avoid the use of call functions, even custom functions, that use getActiveSpreadsheet and other "get active" methods. Instead use openById or other similar methods.
Explanation
SpreadsheetApp.flush() ensures the pending changes made to the script are applied, so it doesn't make sense to put it as the first action of a function.
On the other hand, on Google Apps Script, the user that has opened the spreadsheet establish which spreadsheet is active, on time-driven triggers getActiveSpreadsheet returns null, in other words, we could say that "active" means "being used at this time by an user".
I'm developing an extension for Google Chrome and the problem I'm having is I need to be able to call a JavaScript function that belongs to the webpage that's opened in the tab.
For details, the website is my website, therefore I know that function does exist. That function does a lot of things based on a string value. I want the user to be able to highlight text on any webpage, click a button from the Chrome extension that automatically loads my webpage and calls that function with the highlighted text as it's value.
Here's what I got so far:
chrome.tabs.create({ url: "https://mywebsite.com" }, function (tab) {
var c = "initPlayer('" + request.text + "');"; ////'request.text' is the highlighted text which works
chrome.tabs.executeScript(tab.id, { code: c });
});
But Chrome console says: "Uncaught ReferenceError: initPlayer is not defined."
I know that function does exist as it is in my code on my own website.
Any help is highly appreciated. Thanks!
This happens because pages and content scripts run inside two separate javascript contexts. This means that content scripts cannot acces functions and variables inside a page directly: you'll need to inject a script into the page itself to make it work.
Here is a simple solution:
chrome.tabs.create({url: "https://mywebsite.com"}, function (tab) {
var c = "var s = document.createElement('script');\
s.textContent = \"initPlayer('" + request.text + "');\";\
document.head.appendChild(s);"
chrome.tabs.executeScript(tab.id, {code: c});
});
NOTE: since January 2021, use Manifest V3 with chrome.scripting.executeScript() instead of chrome.tabs.executeScript().
With the above code you will basically:
Create the tab
Inject the code (variable c) into it as a content script that will:
Create a script with the code you want to execute on the page
Inject the script in the page and, therefore, run its code in the page context
So I am using jQuery in my reports and I have a suite of reports that all load thanks to jQuery all at once so the customer feels like the transitions are faster because they don't have to wait between each click. I want to be able to have all reports change based off of a prompt the customer uses. So if they select a specific day, all of the reports in the suite will change to that day. Or if they have a specific area they select, all of the reports go to that area. This will make it so the customer doesn't have to load the parameters in the prompt for each report. I am wonderin if there is a way to do this. I have looked and haven't found anything.
Edit..
So in my report that houses all of the iframes and the value prompt that I have named changeMonth I have this JS
<script>
var report = cognos.Report.getReport("_THIS_");
var radio = report.prompt.getControlByName('monthChange');
var currentRadioValue = radio.getValues()[0]; //Get initial value object
radio.setValidator(validateRadio); //Define function to validate prompt
function validateRadio(values) {
if (values && values.length > 0 && values[0].use != currentRadioValue.use) { //Only do anything if control has value and has changed
currentRadioValue = values[0]; //Assign new value for later comparison
for (var i=0; i<window.frames.length; i++) { //Loop through all iFrames
window.frames[i].changeValue(values[0].use); //Call the changeValue function passing in the radio button value
}
}
return true; //Indicates the prompt is valid
}
</script>
In the reports that I want iframed in I have a value prompt that is a drop down list with this code in an HTML tag.
<script>
function changeValue(str){
var report = cognos.Report.getReport("_THIS_"); //Grab a handle for the report
var control = report.prompt.getControlByName('monthChange'); //Grab a handle for the prompt control
control.addValues([{"use":str,"display":str}]); //Change the prompt to the passed in value
report.sendRequest(cognos.Report.Action.REPROMPT); //Reprompt the page
}
</script>
They were both drop down lists if that matters. I see that you listed them as radio buttons so I will try that here in a moment and let you know if that changed anything. But how I have it setup, is there something else i should be doing?
I was able to get this to work by creating a JavaScript function in each child report which changes a hidden prompt value which the query depends on and then reprompts the page. Here is the portion of the code that needs to be in every child object:
Child Report(s) Code
<script>
function changeValue(str){
var report = cognos.Report.getReport("_THIS_"); //Grab a handle for the report
var control = report.prompt.getControlByName('controlname'); //Grab a handle for the prompt control
control.addValues([{"use":str,"display":str}]); //Change the prompt to the passed in value
report.sendRequest(cognos.Report.Action.REPROMPT); //Reprompt the page
}
</script>
This utilizes the Cognos JavaScript Prompt API added in Cognos version 10.2. The functions getReport, getControlByName, addValues, and sendRequest are all functions provided by Cognos to make working with prompts in JavaScript easier. There's more info here:
Cognos JavaScript Prompt API documentation
In the container report I created a Cognos radio button value prompt. I coded the JavaScript to leverage the built-in onchange validation hook provided by Cognos in the API to run code when the radio button has changed. The code loops through all iFrames and calls the function defined above in each child report passing in the value of the radio button selected.
Container Report Code
<script>
var report = cognos.Report.getReport("_THIS_");
var radio = report.prompt.getControlByName('radio');
var currentRadioValue = radio.getValues()[0]; //Get initial value object
radio.setValidator(validateRadio); //Define function to validate prompt
function validateRadio(values) {
if (values && values.length > 0 && values[0].use != currentRadioValue.use) { //Only do anything if control has value and has changed
currentRadioValue = values[0]; //Assign new value for later comparison
for (var i=0; i<window.frames.length; i++) { //Loop through all iFrames
window.frames[i].changeValue(values[0].use); //Call the changeValue function passing in the radio button value
}
}
return true; //Indicates the prompt is valid
}
</script>
Note that in the above code the strings 'controlname' and 'radio' correspond to the Name property of the prompt controls in question. By default Cognos does not give prompt controls a name. Thus, you have to supply the name after creation. Whatever names you give them the script has to be adjusted accordingly to allow JavaScript to access their Cognos Prompt API objects.
This technique can be modified to take input from any of the variety of prompt controls available in Cognos. Additionally, in theory, the container doesn't even have to be Cognos at all. It could be a standalone Web page with controls that call the JavaScript functions in the iFrames when standard HTML onchange events fire. The only caveat is due to security restrictions browsers do not allow calling of functions within iFrames from containers that have a different domain than the iFrame. This is something to consider when designing a solution.