How to create resuable showModalDialog() in Google App Script? - google-apps-script

I have ModalDialog that prompts for user date range selection in order to generate the appropriate info. I have several menu items that uses the same prompt so I want to reuse the ModalDialog.
// Available method
SpreadsheetApp.getUi().showModalDialog(htmlOutput, 'Options for Menu Item N');
// What I hope is available
SpreadsheetApp.getUI().showModalDialog(htmlOutput, 'Options for Menu Item N', userdataN); // pseudocode
// inside HTML
var userdata = Script.host.environment // pseudocode do something with userdata in HTML
However, the showModalDialog() function does not allow me to pass any user data to the html so I have no way to identify which menu item I need to return the user selection to.
How can I create a reusable ModelDialog in this case?
EDIT:
I realized I can write the environment variable value in a sheet and then later from the HTML retrieve the value, but is there a cleaner way to do it?

You could either pass the user data object as a property of the HtmlTemplate object and use scriptlet syntax (see this answer) or do string interpolation. Personally, I prefer the latter option over using Google's built-in template engine. It's slower but much more flexible.
Suppose we have an HTML page called 'app' in the script editor
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<h1>{{global.app}}</h1>
<div id=container>
<ul>
<li>{{name}}</li>
<li>{{email}}</li>
<li>{{age}}</li>
</ul>
Created by {{global.author}}.
</div>
</body>
</html>
You can call HtmlService methods to serve the template as a string.
//serve HTML template as a string
function getTemplateAsString(filename) {
return HtmlService.createTemplateFromFile(filename).getRawContent();
}
You can then pass the html string to the interpolation function:
var config = {
app: "My app",
author: "me"
};
function interpolateString(htmlString, params) {
//Add global variables to the template
for (var configKey in config) {
if (config.hasOwnProperty(configKey)) {
htmlString = htmlString.replace("{{global." + configKey + "}}", config[configKey]);
}
}
//Insert page-specific parameters
for (var paramsKey in params) {
if (params.hasOwnProperty(paramsKey)) {
htmlString = htmlString.replace("{{" + paramsKey + "}}", params[paramsKey]);
}
}
return htmlString;
}
For the last step, you create the HtmlTemplate object from the resulting string and call the 'evaluate()' method on it. Calling evaluate returns a valid HtmlOutput object instance that you can pass to UI methods
var template = HtmlService.createTemplate(htmlString);
ui.showModalDialog(template.evaluate(), "My dialog");

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.

How to pass varaiable in HTML functions through jquery

this is my html code. group.participants is an array.
result +=`<button class="gsb-${group.id}" onclick="demo(${group.participants})">`+"DEMO"+`</button><br/>`;
this is my simple javascript code to display the array from the parameter
function demo(participants){
alert(participants);
}
this shows me the error
Uncaught SyntaxError: Unexpected end of input
may I know what is the problem
With Jquery you can use the following to pass data to a selector
$(`.gsb-${group.id}`).data(group.participants);
to recover it you just have to call data() method
$(`.gsb-${group.id}`).data();
Finally like each group have different participants, you will have to append first the group button before add the data to it
result.append(`<button class="gsb-${group.id}" onclick="demo(${group.id})">`+"DEMO"+`</button><br/>`);
$(`.gsb-${group.id}`).data(group.participants);
function demo(groupId) {
var participants = $(`.gsb-${groupId}`).data();
console.log(participants);
}
var result = $('#result');
var group = {
id:1,
participants:[
{name:'test1'},
{name:'test2'}
]
}
result.append(`<button class="gsb-${group.id}" onclick="demo(${group.id})">`+"DEMO"+`</button><br/>`);
$(`.gsb-${group.id}`).data(group.participants);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="result"></div>

Error with custom Search and Replace function for Google Sites

I'm trying to use a script to replace a particular string with a different string. I think the code is right, but I keep getting the error "Object does not allow properties to be added or changed."
Does anyone know what could be going wrong?
function searchAndReplace() {
var teams = SitesApp.getPageByUrl("https://sites.google.com/a/directory/teams");
var list = teams.getChildren();
list.forEach(function(element){
page = element.getChildren();
});
page.forEach(function(element) {
var html = element.getHtmlContent();
html.replace(/foo/, 'bar');
element.setHtmlContent = html;
});
};
Try This:
Javascript reference:
The replace() method returns a new string with some or all matches of a pattern replaced by a replacement.
I think the issue here is that forEach cannot change the array that it is called upon. From developer.mozilla.org "forEach() does not mutate the array on which it is called (although callback, if invoked, may do so)."
Try doing it with a regular loop.

Mustache.js iterate over all arrays

I'm still busy trying to setup a JSON file to a HTML website. So if the json changes the changes are dynamically loaded in the HTML. Till this far i'm able to retreive the content and even request some content. But not everything that i want because the markup from the JSON is a bit weird.
Because of the cross-site protection I was not able to do a JSOP request directly, so i solved that with a little trick i saw somewhere. I've created a test.php that simply does:
That way I circumvent the cross-site protection, and everything works well. Only problem is that I can't iterate over all the arrays that i want. Currently i'm using the following script to do a JSOP call and get the data. And the output is a nice description between the <li></li>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
</head>
<body>
<ul id="episodes">
</ul>
<script src="http://cdnjs.cloudflare.com/ajax/libs/mustache.js/0.7.0/mustache.min.js"></script>
<script src="http://ps3scenefiles.com/json/handlebars.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
<script id="episodes-template" type="text/template">
<li>{{description}}</li>
</script>
<script>
$(function() {
$.getJSON('http://ps3scenefiles.com/json/test.php', function(data) {
var template = $('#episodes-template').html();
var info = Mustache.to_html(template, data);
$('#episodes').html(info);
});
});
</script>
</body>
</html>
But when you open the link to the JSON (http://ps3scenefiles.com/json/test.php), you see that the Episodes array has another array with just numbers. How can i create a list like
Episode: 1
Id:13605 Active:true Lang:en Link: url
Id:16525 Active:true Lang:ru Link: url
Episode: 2
Id:14854 Active:true Lang:en Link: url
Id:19445 Active:true Lang:ru Link: url
So to be clear, how can i do a mustache (or handlebars) templating to make it look like the example?
You can use Handlebars helper as mentioned in this answer
Here is a fiddle without styling, that prints out the data you expect (sort of, not all fields).
Here is the helper function -
Handlebars.registerHelper('eachkeys', function(context, options) {
var fn = options.fn, inverse = options.inverse;
var ret = "";
var empty = true;
for (key in context) { empty = false; break; }
if (!empty) {
for (key in context) {
ret = ret + fn({ 'key': key, 'value': context[key]});
}
} else {
ret = inverse(this);
}
return ret;
});

Extract the contents of a div using Flash AS3

I have a SFW embedded in a PHP page. There is also a div on the page with id="target".
I want to access the content of that div (ie: the characters inside it) and hold them as a String variable in AS3. How can I do this?
My attempt so far
import flash.external.ExternalInterface;
var myDivContent = ExternalInterface.call("function(){ return document.GetElementById('target');}");
var myDivContent2:String = myDivContent.toString();
test_vars.text = myDivContent2; //Dynamic text output
I don't think you can define a function in the ExternalInterface.call() method. You have to call a function by name which already exists in the JavaScript.
So I'd create some JavaScript code like this:
function getTargetContent()
{
return document.getElementById('target').innerHTML;
}
And then in your Flash,
var myDivContent = ExternalInterface.call("getTargetContent");
Note that document.getElementById('target') only returns the reference to that div, not the contents within. So if you don't return .innerHTML then the Flash will get an object which may not be usable (although I haven't actually tried doing this).
The easiest way to do this is as Allan describes, write a Javascript function to sit on the page and return the required value to you.
Of course, if you can't edit the page content, only the flash, then you do need to pass the function itself, which will actually have to be forced into the page though JavaScript injection. An example for your case, which I have not tested:
//prepare the JavaSctipt as an XML object for Dom insertion
var injectCode:XML =
<script>
<![CDATA[
function() {
getElementContent = function(elementID) {
return document.getElementById(elementID).innerHTML;
}
}
]]>
</script>;
//inject code
ExternalInterface.call(injectCode);
//get contents of 'divA'
var divAContent:String = ExternalInterface.call('getElementContent','divA') as String;
//get contents of 'spanB'
var spanBContent:String = ExternalInterface.call('getElementContent','spanB') as String;
You're almost there :
var res : String = ExternalInterface.call("function(){return document.getElementById('target').outerHTML}");
If you only want the content of your target, use innerHTML instead of outerHTML.