How do I alter the UI of a Gmail Add On from a callback? - gmail-addons

New to gmail add ons and have a question which I am sure is pretty basic: how do I change the UI of my add on from a callback function?
More specifically, I have a button which renders in a section on my card:
section.addWidget(
CardService.newTextButton()
.setText('UNSUBSCRIBE')
.setOnClickAction(
CardService.newAction()
.setFunctionName('unsubscribe')
.setParameters({email: sender.email})));
As you can see, I call a function called "unsubscribe" when it is clicked which in turn calls a 3rd party API endpoint. Upon the 200 response from that endpoint, I want to hide the "unsubscribe" button and show a message to the user stating that the unsubscribe request went through successfully. Currently, I don't know how to access the card sections so I am showing a notification in stead:
function unsubscribe(e){
var parameters = e.parameters;
var email = parameters['email'];
var data = {
"code":"XXXX",
"email": email
};
var options = {
'method' : 'post',
'contentType': 'application/json',
'payload' : JSON.stringify(data)
};
var response = UrlFetchApp.fetch('https://someurl.com', options);
if(response.getResponseCode() == 200){
return CardService.newActionResponseBuilder()
.setNotification(CardService.newNotification().setType(CardService.NotificationType.INFO)
.setText(email + " has been unsubscribed from future notices."))
.build();
}
return CardService.newActionResponseBuilder()
.setNotification(CardService.newNotification()
.setType(CardService.NotificationType.WARNING)
.setText("Something went wrong"))
.build();
}
It would be much better to hide the UNSUBSCRIBE button on success instead and display a success message in line. How would I do this?
Thanks in advance.

As of now, we cannot dynamically update sections of the card. Hence, you cannot directly hide the button and display some message. But as a workaround, you can redraw the whole card(which won't include the button and will include the message) using Navigation class and updateCard method.
If you havn't used navigation class yet, this link might be useful.

Related

Is this possible to send and get back the value from google app script to html page without rendering html output?

After much discussion and R&D, image cropping is not possible with Google APP scripts. So I decided to try one using the Canvas API.
I am trying to pass the value from server script(.gs) to the HTML file and get back the value in the server side script without opening HTML output as in sidebar or model/modelLess dialog box. You can say silently call HTML, complete the process and return the value to server script method.
I tried but getFromFileArg() is not running when i am running the callToHtml().
Is this possible with below script? what you will suggest?
Server side (.gs)
function callToHtml() {
var ui = SlidesApp.getUi();
var htmlTemp = HtmlService.createTemplateFromFile('crop_img');
htmlTemp["data"] = pageElements.asImage().getBlob();
var htmlOutput = htmlTemp.evaluate();
}
function getFromFileArg(data) {
Logger.log(data);
}
crop_img.html template :
<script>
var data = <?= data ?>;
//call the server script method
google.script.run
.withSuccessHandler(
function(result, element) {
element.disabled = false;
})
.withFailureHandler(
function(msg, element) {
console.log(msg);
element.disabled = false;
})
.withUserObject(this)
.getFromFileArg(data);
</script>
You cannot "silently" call the HTML this way, no.
The HTML needs to go to the user and the user is not inside of your web app, but Google's web app (Slides), so you have to play by their rules.
You need to use one of the available UI methods such as showSidebar. You could have the displayed HTML be a spinner or message like "processing..." while the JavaScript runs.
function callToHtml() {
var ui = SlidesApp.getUi();
var htmlTemp = HtmlService.createTemplateFromFile('crop_img');
htmlTemp["data"] = pageElements.asImage().getBlob();
ui.showSidebar(htmlTemp.evaluate());
}

Add JQuery reference to custom function

I want to test calling an API in a custom function for Google Sheets. code.gs is as follows:
function TApi(input) {
var url = "https://api.nytimes.com/svc/search/v2/articlesearch.json";
url += '?' + $.param({
'api-key': "cdaa59fea5f04f6f9fd8fa551e47fdc4",
'q': "MIT"
});
$.ajax({
url: url,
method: 'GET',
}).done(function(result) {
return result;
console.log(result);
}).fail(function(err) {
throw err;
});
}
But when I call =TAPI() in a sheet cell, it returns an error ReferenceError: "$" is not defined. (line 22). I guess we need to add a link to JQuery. Does anyone know how to do this?
You can only use JQuery on client side scripts which use the HTML service. It is not available server side. There is a blurb about using it in the HTML Services Best Practices.
It's not possible. You must build either a web app or custom UI (sidebar or dialog) using HtmlService and do the processing on the client. Because your code runs on Google servers, there are no 'window' or 'document' objects. DOM and BOM are only accessible on the client.
In fact, feel free to do the following little experiment. Open your browser console (I'm using Chrome developer tools) and type in
console.log(this); //this logs global object
Here's the output
This is the 'window' object used by jQuery for navigating the DOM tree. jQuery is simply a JS library that builds on top of existing DOM manipulation methods and CSS selectors.
Next, open any GAS file, run the following function and check the Logs (Ctrl + Enter):
function test() {
Logger.log(this);
}
And here's the output.
As you can see, the global object in this context consists of Google-defined pseudo classes (GAS services).
You can use urlFetch app. Try the below snippet
function fetchURL() {
try {
var url = "https://api.nytimes.com/svc/search/v2/articlesearch.json";
url += '?api-key=cdaa59fea5f04f6f9fd8fa551e47fdc4&q=MIT';
var params = {
'method': 'get',
'contentType': 'application/json',
'muteHttpExceptions': true
}
var response = UrlFetchApp.fetch(url, params);
Logger.log(response)
} catch (e) {
Logger.log(e)
}
}

How to send information from window to the devtool in chrome extension

In my app I have a namespaced application and there's information or metadata myApp carries on it that might be useful to devpane.
window.myApp = new App();
How can I relay or send the following information to the devtool.js?
window.myApp.metadata; // information
And can I send a request from the devtool with a function that customizes the serialization of that metadata?
I've seen similar posts with the solution below, which returns null when I tried it.
chrome.devtools.inspectedWindow.eval("window.myApp", {
useContentScriptContext: true
})
NOTE: If a sample template can be provided that would be wonderful.
This is how I've solved this. It feels more complicated than necessary, but it does work.
In the context of the inspected window
Based on this question.
This is where you've got access to window.myApp.metadata and can put it into the data object.
var event = new CustomEvent("RebroadcastExtensionMessage", {data: ""});
window.dispatchEvent(event);
In the content script
This just forwards the data to the background page.
window.addEventListener("RebroadcastExtensionMessage", function(evt) {
chrome.runtime.sendMessage(evt)
}, false);
In the background page
Based on the Chrome docs.
chrome.runtime.onConnect.addListener(function(devToolsConnection) {
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
devToolsConnection.postMessage(request)
});
})
In devtools.js
var backgroundPageConnection = chrome.runtime.connect({
name: "devtools-page"
});
backgroundPageConnection.onMessage.addListener(function (message) {
// Data has arrived in devtools page!!
});

Chrome extension create new tab and send message from popup.js to content script of new tab

I am developing a chrome extension where my popup.js receives a message from a content script on the current page and creates an array. Then on a button press, popup.js creates a new tab (which has a content script running) and sends that content script a message containing the array.
My popup.js:
//this message is sent from a different content script (for current page), not shown here
chrome.runtime.onMessage.addListener(function(request, sender) {
if (request.action === "getSource") {
var arr = JSON.parse(request.source);
//create new tab
chrome.tabs.create({url: "newtab.html"}, function(tab){
//send message to new tab
chrome.tabs.sendMessage(tab.id{
action: "getDataArray",
source: JSON.stringify(arr)
});
}
});
newtab-contentscript.js:
$(document).ready( function() {
chrome.runtime.onMessage.addListener(function(request, sender) {
if (request.action === "getDataArray") {
$("#result").html(JSON.parse(request.source));
}
});
newtab.html:
<script src="newtab-contentscript.js"></script>
Problem: The newtab-contentscript.js never seems to receive the message.
Are the any mistakes with how I am creating a tab or sending the message. Do you have any suggestions to how to fix this issue?
As we discussed in the comments, I guess maybe $(document).ready is too late to receive messages from chrome.tabs.sendMessage, you can test it by comparing timestamps of console.log inside the callback and on the first line of the new tab's content scripts, as #wOxxOm mentioned.
I just suggest moving message logic to background (event) page and starting the message passing from newtab-contentscript.js, in which you could control when to start sending the message.
A sample code
background.js
let source = null;
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
// sent from another content script, intended for saving source
if(request.action === 'putSource') {
source = request.source;
chrome.tabs.create({ url: 'newtab.html' });
}
// sent from newtab-contentscript, to get the source
if(request.action === 'getSource') {
sendResponse({ source: source });
}
});
newtab-contentscript.js
chrome.runtime.sendMessage({action: 'getSource'}, function(response) {
$('#result').html(response.source);
});

Sending message from popup.js in Chrome extension to background.js

What is the proper way to send a message (and get a response) to background.js from popup.js in a Chrome extension? Every method I try ends up with an error that:
"Port: Could not establish connection. Receiving end does not exist."
I would prefer to use chrome.extension.sendMessage() over chrome.extension.connect() with port.postMessage(), but neither method seems to have worked.
What I am trying to do is wire a button in the popup.html to call into some javascript in popup.js which calls back to background.js in an effort to get info about the currentTab() that was topMost (ie:to get the current URL string to show in the popup.html)
Right now in popup.js (wired to the action of the button) I have:
function getURL()
{
chrome.extension.sendMessage({greeting: "GetURL"},
function(response) { tabURL = response.navURL });
$("#tabURL").text(tabURL);
}
In background.js I have:
chrome.extension.onMessage.addListener( function(request,sender,sendResponse)
{
if( request.greeting == "GetURL" )
{
var tabURL = "Not set yet";
chrome.tabs.getCurrent(function(tab){
tabURL = tab.url;
});
sendResponse( {navURL:tabURL} );
}
}
Any ideas?
Just to clarify, we talking about communication between popup page from browserAction and background script?
Anyway you have quite a few errors in your code.
First your totally ignore the fact that all callbacks in chrome api are asynchronous.
In background page
var tabURL = "Not set yet";
chrome.tabs.getCurrent(function(tab){
tabURL = tab.url;
}); //this will be invoked somewhere in the future
sendResponse( {navURL:tabURL} );
//navUrl will be always Not set yet because callback of getCurrent hasn't been called yet
Same in popup.js
chrome.runtime.sendMessage({greeting: "GetURL"},
function(response) { tabURL = response.navURL });//callback will be invoked somewhere in the future
$("#tabURL").text(tabURL)//tabURL will display something totally different from what you have been expected
Second error is that chrome.tabs.getCurrent doesn't give you the current tab selected by user in main window. The docs says:
Gets the tab that this script call is being made from. May be
undefined if called from a non-tab context (for example: a background
page or popup view).
So you will get undefined for all of your requests, because you call it in background page. What you need to do is to use method chrome.tabs.query to obtain currently active tabs.
So after fixing all problems new code should look something like this:
background.js
chrome.runtime.onMessage.addListener( function(request,sender,sendResponse)
{
if( request.greeting === "GetURL" )
{
var tabURL = "Not set yet";
chrome.tabs.query({active:true},function(tabs){
if(tabs.length === 0) {
sendResponse({});
return;
}
tabURL = tabs[0].url;
sendResponse( {navURL:tabURL} );
});
}
}
popup.js
function getURL() {
chrome.runtime.sendMessage({greeting: "GetURL"},
function (response) {
tabURL = response.navURL;
$("#tabURL").text(tabURL);
});
}