Google Script Pass variable to templated HTMLService - google-apps-script

I have a script which I have published as a web app. I wanted to change a default setting based on the URL used to run the web app. I am already opening one of two forms, but on one form, I want to have a radio button selected based on a second passed parameter. In the server side gs file I have:
function doGet(passed) {
switch(passed.parameter.form) {
case 'single':
var result = HtmlService.createTemplateFromFile('Single').evaluate();
result.setHeight(550);
result.setWidth(565);
break;
case 'grid':
default:
var result=HtmlService.createTemplateFromFile('GridView').evaluate();
result.setHeight(550);
result.setWidth(1285);
}
return result;
}
On Google's HTML Service: Templated HTML page there is a section Pushing variables to templates which seems to be what I want but I can't get it to work.
in my Single.html file I have:
<body>
<? if (data === "Ex") { ?> Existing <? } else { ?> New <? } ?>
... </body>
The html portion above is overly simplified and getting this to work will get me to my ends, which is a much larger page with input areas, etc.
In an attempt to get "Existing" to display in the resulting page, I have changed the above code to:
function doGet(passed) {
switch(passed.parameter.form) {
case 'single':
var result = HtmlService.createTemplateFromFile('Single');
result.setHeight(550);
result.setWidth(565);
result.data = 'Ex';
return result.evaluate().setSandboxMode(HtmlService.SandboxMode.IFRAME);
break;
case 'grid':
default:
var result=HtmlService.createTemplateFromFile('GridView').evaluate();
result.setHeight(550);
result.setWidth(1285);
return result;
}
}
and get errors
TypeError: Cannot find function setHeight in object HtmlTemplate. (line 131, file "Code")
Even if I remove the setHeight and setWidth resulting in just have the data as shown on the above referenced page I get errors.
Has anyone passed a variable to a page like this?

Looks like the method calls are just in the wrong order. The result.data = 'Ex'; should be before the .evaluate(), but the .setHeight() and .setWidth() must be applied afterwards. Modifying your last example slightly:
case 'single':
var result = HtmlService.createTemplateFromFile('Single');
result.data = 'Ex';
return result.evaluate()
.setHeight(550)
.setWidth(565)
.setSandboxMode(HtmlService.SandboxMode.IFRAME);
break;
I tracked down the basic error to a missed } after your if statement in your HTML file. The code should look like:
<? if (data === "Ex") { ?> Existing <? } else { ?> New <? } ?>

Related

Google Apps Script - Passing Variable from a Script to an HTML then back to a Script and one last time to an HTML

I am having troubles passing 1 variable (dateToExport) into an HTML "showConflict_HTML" and then running a function/script within that HTML by passing dateToExport.
1 Script ("exportButton")
1 HTML ("showConflict_HTML")
The Process goes like this:
The script "exportButton" runs passing (dateToExport) into it
And then the "showConflict_HTML" html pops up in which the user clicks a button "Yes, Overwrite and Export"
The script "exportButton" then runs again and passes the dateToExport into it again
When I click the "Yes, Overwrite and Export", nothing happens and the "google.script.run.exportOrderData(1, dateToExport_captured);" does not run in the HTML. Also there is no error given so I can not figure out why. Anyone have any idea why?
function exportOrderData(override, dateToExport) {
if(override == 1){
execute_exportOrderData();
easterEgg = 1;
}
else if(override == 0){
var userInterface = HtmlService
.createHtmlOutputFromFile('showConflict_HTML');
userInterface.dateToExportFromServerTemplate = dateToExport;
SpreadsheetApp.getUi()
.showModelessDialog(userInterface, 'Existing Customer Order(s) on ' + dateWithConflict);
}
}
<!-- "showConflict_HTML". This HTML file is will run the exportOrderData function once the Override button ("my_button") has been clicked !-->
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<link href="https://fonts.googleapis.com/css2?family=Comic+Neue&display=swap" rel="stylesheet">
</head>
<body>
<button id='my_button'>Yes, Overwrite and Export</button>
<script>
`THIS SHOULD PASS THE dateToExport Varaible so we can access it in this HTML Script`
var dateToExport_captured = dateToExportFromServerTemplate;
// Once the Button is Clicked, the following occurs
document.getElementById('my_button').addEventListener('click', _ => {
// Once the Button is Clicked, the Loading Circle Effect Function will start running here
google.script.run.loadingCircleEffect();
google.script.run.exportOrderData(1, dateToExport_captured);
});
</script>
</body>
</html>
function exportOrderData(override, dateToExport) {
Try using force-printing scriptlets in your HTML, along the lines of this:
var dateToExport_captured = <?!= JSON.stringify(dateToExportFromServerTemplate) ?>;
Notes:
JSON.stringify can be omitted if your value is a string or a number.
Per the documentation, you should NOT use this technique if dateToExport comes from untrusted users. If it's your own system that generates it then you should be fine.
Although this is not an answer it may shed some light on what is wrong with your code.
To pass dateToExportFromServerTemplate to the html page you need to change the code in exportOrderData as below using templated html createTemplateFromFile
var userInterface = HtmlService.createTemplateFromFile('showConflict_HTML');
userInterface.dateToExportFromServerTemplate = dateToExport;
userInterface = userInterface.evaluate();
For the page to receive the variable you need to use a scriptlet.
var dateToExport_captured = <?= dateToExportFromServerTemplate ?>
But what I don't understand is dateToExportFromServerTemplate never changes so why display a new page?
So I was able to fix this by using localStorage.setItem('dateToExport_transfer',dt) in the HTML where the user selects the date and then in my "showConflict_HTML" HTML I call var dateToExport_captured = localStorage.getItem('dateToExport_transfer') to grab that date from the other HTML.

LexResponse output does not understand HTML data

I'm having a problem trying to get my AWS Lambda function to successfully output a series of HTML links when its running a SQL Query.
private string GetEventSearchResults(ILambdaContext context, List<Event> events, string CustomerNumber)
{
var result = string.Empty;
var link = string.Empty;
if (events.Count > 0)
{
result = $"Events for {CustomerNumber}:";
foreach (var evt in events)
{
link = "http://localhost/event/" + $"{evt.ID}";
result += $"<br>Event: {evt.ID} - Status: {evt.Status}";
}
}
else
{
result = "No Data found matching your query";
}
return result;
}
When this method is called by my Lambda function as a LexResponse,
replyMessage = GetEventSearchResults(context, eventList, query.CustomerNumber);
return Close(
sessionAttributes,
"Fulfilled",
new LexResponse.LexMessage
{
ContentType = "PlainText",
Content = replyMessage
}
);
This response is then rendered in my HTML page by a Javascript function. Relevant portion of the Javascript that renders the response:
function showResponse(lexResponse) {
var conversationDiv = document.getElementById('conversation');
var responsePara = document.createElement("P");
responsePara.className = 'lexResponse';
if (lexResponse.message) {
responsePara.appendChild(document.createTextNode(lexResponse.message));
responsePara.appendChild(document.createElement('br'));
}
if (lexResponse.dialogState === 'ReadyForFulfillment') {
responsePara.appendChild(document.createTextNode(
'Ready for fulfillment'));
// TODO: show slot values
}
conversationDiv.appendChild(responsePara);
conversationDiv.scrollTop = conversationDiv.scrollHeight;
}
However, the output shown by the Lex bot is as shown below:
Lex Bot Output
Can anyone please help me understand what exactly is going on? Is the content type of the Lex Response responsible for this? (there's only plaintext and SSML available for Lex Response so I can't change that)
Also, if possible, can anyone please explain how to fix this if at all possible? Thanks!
Your code is correct and output is also correct.
However the console window is not able to render the HTML part of your result.
The client on which you will deploy the chatbot, is responsible for rendering the output. For example, if you respond with a ResponseCard, console or website will not be able to render it correctly but it will be displayed correctly on Facebook and Slack. So, if you integrate your chatbot on some website it will show the links in your output correctly as you desired.
You can try to integrate your chatbot with Slack or Facebook first, to see the rendering of output.
Hope it helps.
After further trial and error, I managed to get a solution that works for me.
function showResponse(lexResponse) {
var conversationDiv = document.getElementById('conversation');
var responsePara = document.createElement("P");
responsePara.className = 'lexResponse';
if (lexResponse.message) {
var message = lexResponse.message.replace(/"/g, '\'');
responsePara.innerHTML = message;
responsePara.appendChild(document.createElement('br'));
}
conversationDiv.appendChild(responsePara);
conversationDiv.scrollTop = conversationDiv.scrollHeight;
}
By making the LexResponse an Inner HTML, it fixed the markup of the text and thus the link can be seen everytime.

Accessing DOM object properties from Chrome's content script

I ran into a strange problem with a content script. The content script is defined as "run_at" : "document_end" in the manifest. After a page is loaded the script inserts an object tag into the page (if the tag with predefined id does not exist yet), and sets some properties in it, such as type, width, height, innerHTML, and title. All works fine here.
function checkForObject()
{
var obj = document.getElementById("unique_id");
if(obj == null)
{
var d = document.createElement("object");
d.id = "unique_id";
d.width = "1";
d.height = "1";
d.type = "application/x-y-z";
d.title = "1000";
d.style.position = "absolute";
d.style.left = "0px";
d.style.top = "0px";
d.style.zIndex = "1";
document.getElementsByTagName("body")[0].appendChild(d);
}
}
checkForObject();
I see the new object in the page html-code with proper values in its properties.
Some time later I need to read the title property of the object in the same content script. The code is simple:
function ReadTitle()
{
var obj = document.getElementById("unique_id");
var value = obj.title; // breakpoint
console.log(value);
// TODO: want to use proper title value here
}
The function is called from background.html page:
chrome.tabs.onActivated.addListener(
function(info)
{
chrome.tabs.executeScript(info.tabId, {code: 'setTimeout(ReadTitle, 250);'});
});
Unfortunately, in ReadTitle I'm getting not what I expect. Instead of current value of the title I see the logged value is:
function title() { [native code] }
If I set a breakpoint at the line marked by // breakpoint comment, I see in the watcher that all object properties including the title are correct. Nevertheless, the variable value gets the abovementioned descriptive string.
Apparently, I have missed something simple, but I can't figure it out.
The answer. It was a bug in the npapi plugin, which hosts the object of used type. My apologies for all who have read the question with intention to help.
The NPAPI plugin used in the object erroneously reported title as supported method.

JSON API to show Advanced Custom Fields - WordPress

I am developing a magazine WordPress site that will have a json feed for a Mobile App. I set the backend up using Advanced Custom Fields with a Repeater Field for Multiple Articles and Multiple Pages within each article. http://www.advancedcustomfields.com/add-ons/repeater-field/
I am using the JSON API but this does not include any of my custom fields. Is there currently a plugin that can do this?
#Myke: you helped me tremendously. Here's my humble addition:
add_filter('json_api_encode', 'json_api_encode_acf');
function json_api_encode_acf($response)
{
if (isset($response['posts'])) {
foreach ($response['posts'] as $post) {
json_api_add_acf($post); // Add specs to each post
}
}
else if (isset($response['post'])) {
json_api_add_acf($response['post']); // Add a specs property
}
return $response;
}
function json_api_add_acf(&$post)
{
$post->acf = get_fields($post->id);
}
Update for Wordpress 4.7
With the release of Wordpress 4.7 the REST functionality is no longer provided as a distinct plugin, rather its rolled in (no plugin required).
The previous filters don't appear to work. However the following snippet does (can be in your functions.php):
>= PHP 5.3
add_filter('rest_prepare_post', function($response) {
$response->data['acf'] = get_fields($response->data['id']);
return $response;
});
< PHP 5.3
add_filter('rest_prepare_post', 'append_acf');
function append_acf($response) {
$response->data['acf'] = get_fields($response->data['id']);
return $response;
};
Note the filter is a wild card filter, applied like
apply_filters("rest_prepare_$type", ...
so if you have multiple content types (custom), you will need to do:
add_filter('rest_prepare_multiple_choice', 'append_acf');
add_filter('rest_prepare_vocabularies', 'append_acf');
function append_acf($response) {
$response->data['acf'] = get_fields($response->data['id']);
return $response;
};
Note It appears that rest_prepare_x is called per record. So if you are pinging the index endpoint, it will be called multiple times (so you don't need to check if its posts or post)
Came here by searching with the same question. This isn't totally vetted yet but I think this is getting on the right path. Check it out.
I have one less nested level than you do so this might need altered a bit. But the JSON API plugin has a filter called json_api_encode. I have a repeater called specifications that looks like this.
http://d.pr/i/YMvv
In my functions file I have this.
add_filter('json_api_encode', 'my_encode_specs');
function my_encode_specs($response) {
if (isset($response['posts'])) {
foreach ($response['posts'] as $post) {
my_add_specs($post); // Add specs to each post
}
} else if (isset($response['post'])) {
my_add_specs($response['post']); // Add a specs property
}
return $response;
}
function my_add_specs(&$post) {
$post->specs = get_field('specifications', $post->id);
}
Which appends a custom value to the JSON API output. Notice the get_field function from ACF works perfectly here for bringing back the array of the repeater values.
Hope this helps!
There is now a small plugin which adds the filter for you.
https://github.com/PanManAms/WP-JSON-API-ACF
I'm not sure if you're still interested in a solution, but I was able to modify the json-api plugin models/post.php file to display the repeater data as an array. This is a modification of a modification made by http://wordpress-problem.com/marioario-on-plugin-json-api-fixed-get-all-custom-fields-the-right-way/
replace the set_custom_fields_value() function with the following:
function set_custom_fields_value() {
global $json_api;
if ($json_api->include_value('custom_fields') && $json_api->query->custom_fields) {
// Query string params for this query var
$params = trim($json_api->query->custom_fields);
// Get all custom fields if true|all|* is passed
if ($params === "*" || $params === "true" || $params === "all") {
$wp_custom_fields = get_post_custom($this->id);
$this->custom_fields = new stdClass();
// Loop through our custom fields and place on property
foreach($wp_custom_fields as $key => $val) {
if (get_field($key)) {
$this->custom_fields->$key = get_field($key);
} else if ($val) {
// Some fields are stored as serialized arrays.
// This method does not support multidimensionals...
// but didn't see anything wrong with this approach
$current_custom_field = #unserialize($wp_custom_fields[$key][0]);
if (is_array($current_custom_field)) {
// Loop through the unserialized array
foreach($current_custom_field as $sub_key => $sub_val) {
// Lets append these for correct JSON output
$this->custom_fields->$key->$sub_key = $sub_val;
}
} else {
// Break this value of this custom field out of its array
// and place it on the stack like usual
$this->custom_fields->$key = $wp_custom_fields[$key][0];
}
}
}
} else {
// Well this is the old way but with the unserialized array fix
$params = explode(',', $params);
$wp_custom_fields = get_post_custom($this->id);
$this->custom_fields = new stdClass();
foreach ($params as $key) {
if (isset($wp_custom_fields[$key]) && $wp_custom_fields[$key][0] ) {
$current_custom_field = #unserialize($wp_custom_fields[$key][0]);
if (is_array($current_custom_field)) {
foreach($current_custom_field as $sub_key => $sub_val) {
$this->custom_fields->$key->$sub_key = $sub_val;
}
} else {
$this->custom_fields->$key = $wp_custom_fields[$key][0];
}
}
}
}
} else {
unset($this->custom_fields);
}
}
Current version of ACF prints out a custom_fields object on a call to the JSON API, containing all the fields relative to the Post or Page. I edited #Myke version to add specific custom fields from the ACF Option page to each Post or Page. Unfortunately there is not get_fields() function for the whole Option Page so you'll have to edit it depending on your fields structure.
add_filter('json_api_encode', 'json_api_encode_acf');
function json_api_encode_acf($response) {
if (isset($response['posts'])) {
foreach ($response['posts'] as $post) {
json_api_add_acf($post); // Add specs to each post
}
}
else if (isset($response['post'])) {
json_api_add_acf($response['post']); // Add a specs property
}
else if (isset($response['page'])) {
json_api_add_acf($response['page']); // Add a specs to a page
}
return $response;
}
function json_api_add_acf(&$post) {
$post->custom_fields->NAME_OF_YOUR_CUSTOM_FIELD = get_field( 'NAME_OF_YOUR_CUSTOM_FIELD', 'option' );
}

MediaWiki Extension question / suggestion

Complete beginner here. I want to create a new tab on each page that has a custom action. When clicked, it takes you to a new page which has custom HTML on it along with the text or the original article.
So far I could create a new Tab and could give a custom action mycustomaction to it. I am pasting what I did so far here. Please let me know if I am using the correct hooks etc. and what is a better way to achieve this basic functionality.
So far with their docs I have done this:
#Hook for Tab
$wgHooks['SkinTemplateContentActions'][] = 'myTab';
#Callback
function myTab( $content_actions) {
global $wgTitle;
$content_actions['0'] = array(
'text' => 'my custom label',
'href' => $wgTitle->getFullURL( 'action=mycustomaction' ),
);
return true;
}
#new action hook
$wgHooks['UnknownAction'][] = 'mycustomaction';
#callback
function mycustomaction($action, $article) {
echo $action;
return true;
}
This gives me error:
No such action
The action specified by the URL is invalid. You might have mistyped the URL, or followed an incorrect link. This might also indicate a bug in the software used by yourplugin
What I was doing wrong:
$content_actions[‘0’] should simply be $content_actions[] (minor nitpick)
$content_actions is passed-by-reference, it should be function myTab( &$content_actions ) {}
mycustomaction() should do something along the lines of
if ( $action == ‘mycustomaction’ ) {
do stuff; return false;
}
else {
return true;
}
It should use $wgOut->addHTML() instead of echo
Thanks a lot everyone for your help!