How to build dynamic dropdowns in configuration setup? - google-apps-script

I'm new to Google Data Studio and looking into building a community connector for our Saas service.
For the configuration section, I need to use the Stepped Configuration process. Basically, I nested set of drop-down lists.
However, I need the data to populate those lists to come from my API. I have the REST service endpoints defined, but I cannot find any documenation/examples of how I'd configure this in the getConfig section of the community connector.
Does anyone have a working example I could use as reference?
In reviewing the documentation, there is a section on stepped configurations, which is what I am looking for. You can find that example here: https://developers.google.com/datastudio/connector/stepped-configuration#dynamic_dropdowns
In this example, they show the following for defining the dropdown values.
Notice for the states, they have hard-coded the values for "Illinois" and "California".
My question is, how can I dynamically call API to retrieve values to populate this list? I have 3 nested dropdowns, each with a separate API call, using the answer from previous dropdown to drive the next.
For example first API might be http://myapi.com/countries which returns list of countries.
When they select country, next API call might be http://myapi.com/states?country=US
etc.
config.newSelectSingle()
.setId("state")
.setName("State")
// Set isDynamic to true so any changes to State will clear the city
// selections.
.setIsDynamic(true)
.addOption(config.newOptionBuilder().setLabel("Illinois").setValue("IL"))
.addOption(config.newOptionBuilder().setLabel("California").setValue("CA"));
if (!isFirstRequest) {
var city = config.newSelectSingle()
.setId("city")
.setName("City");
var cityOptions = optionsForState(configParams.state);
cityOptions.forEach(function(labelAndValue) {
var cityLabel = labelAndValue[0];
var cityValue = labelAndValue[1];
city.addOption(config.newOptionBuilder().setLabel(cityLabel).setValue(cityValue));
});
}
return config.build();
}

Worked through the issues I was having. For others who might have hit similiar issues, here's my working getConfig() method.
function getConfig(request) {
var config = cc.getConfig();
var configParams = request.configParams;
var isFirstRequest = configParams === undefined;
if (configParams ===undefined || configParams.tab ===undefined) {
config.setIsSteppedConfig(true);
}
var url ='https://<yourAPIURL>';
var userProperties = PropertiesService.getUserProperties();
var key = userProperties.getProperty('dscc.key');
var mykey ="Bearer " + key
var options = {
"method" : "GET",
"headers" : {
"AUTHORIZATION" : mykey,
"cache-control": "no-cache"
}
};
var response = UrlFetchApp.fetch(url,options);
var parsedResponse = JSON.parse(response);
var zoneControl = config.newSelectSingle()
.setId("zone")
.setName("Zone")
.setIsDynamic(true);
parsedResponse.map(function(itm) {
zoneControl.addOption(config.newOptionBuilder().setLabel(itm.name).setValue(itm.id))
});
if(configParams !==undefined && configParams.zone !==undefined){
var blockurl ='https://<yourAPIURL>?zoneid='+ configParams.zone;
var blockResponse = UrlFetchApp.fetch(blockurl,options);
var parsedBlockResponse = JSON.parse(blockResponse);
var blockControl = config.newSelectSingle()
.setId("block")
.setName("Block")
.setIsDynamic(true);
parsedBlockResponse.map(function(itm) {
blockControl.addOption(config.newOptionBuilder().setLabel(itm.name).setValue(itm.blockKey))
});
}
if(configParams !==undefined && configParams.block !==undefined){
var taburl =''https://<yourAPIURL>?blockKey='+ configParams.block;
var tabResponse = UrlFetchApp.fetch(taburl,options);
var parsedTabResponse = JSON.parse(tabResponse);
var tabControl = config.newSelectSingle()
.setId("tab")
.setName("Tab")
parsedTabResponse.map(function(itm) {
tabControl.addOption(config.newOptionBuilder().setLabel(itm.name).setValue(itm.internalname))
});
}
return config.build();
}

without testing the code:
function getConfig(request) {
var configParams = request.configParams;
var isFirstRequest = configParams === undefined;
var lst=["A","B","C"]; // your values obtained from REST
var tmp=config.newSelectSingle(); //add element to side
var element=tmp.setId("state").setName("State").setIsDynamic(true); // set name and id
for(var i in lst) // set all the values:
{
element = element.addOption(config.newOptionBuilder().setLabel(lst[i]).setValue(lst[i]))
}
if(isFirstRequest || configParams.state==undefined) // no state selected yet
{
config.setIsSteppedConfig(true); // stop here
}
else
{
// next dropdown element,
// Rest API with element set to: configParams.state
var lst2= ["x","y","z"]
var tmp2=config.newSelectSingle(); //add element to side
var element2=tmp2.setId("element2").setName("Element 2 depends on "+configParams.state).setIsDynamic(true); // set name and id
for(var i in lst2) // set all the values:
{
element2 = element2.addOption(config.newOptionBuilder().setLabel(lst2[i]).setValue(lst2[i]))
}
// code for 3rd
}
}
If the user changes the first dropdown value alle other drop downs have to be reset. This may be a bit tricky.

Related

Start model browser in Forge viewer collapsed when loading several models

I have tried to start the model browser with all nodes collapsed when loading several aggregated models, but it do no collapse all nodes. Is there any way to do this?
Try the code below on these model: https://wallabyway.github.io/federatedmodels-v7/
var ext = NOP_VIEWER.getExtension('Autodesk.ModelStructure')
ext._modelstructure.options.startCollapsed = true
Try to use this one instead. The ModelStructralPanel will read options in its constructor only.
var viewer = new Autodesk.Viewing.GuiViewer3D(container, {startCollapsed: true});
var ext = viewer.getExtension('Autodesk.ModelStructure');
// or
// viewer.unloadExtension('Autodesk.ModelStructure');
// var ext = await viewer.loadExtension('Autodesk.ModelStructure', {startCollapsed: true});
Workaround:
Add this code snippet before opening the modelstructure panel.
var ext = viewer.getExtension('Autodesk.ModelStructure');
ext._modelstructure.addVisibilityListener( show => {
if( show && (!ext._modelstructure.uiCreated) ) {
ext._modelstructure.tree.delegates.forEach( d => ext._modelstructure.tree.setAllCollapsed( d, true ) )
}
});
When creating the viewer, pass the following option to it:
var viewer = new Autodesk.Viewing.GuiViewer3D(container, {modelBrowserStartCollapsed: true});
It should cascade until reaching the model browser.
Background
The option "modelBrowserStartCollapsed" is passed on from the Viewer3D constructor up until the ModelStructureExtension, where it changes name to "startCollapsed" as it is passed to the ViewerModelStructurePanel.
proto.restoreDefaultPanel = function () {
var config = this.viewer.config;
var options = {
docStructureConfig: config.docStructureConfig,
hideSearch: (0, _src_compat__WEBPACK_IMPORTED_MODULE_2__.isMobileDevice)(),
excludeRoot: config.modelBrowserExcludeRoot,
startCollapsed: config.modelBrowserStartCollapsed // HERE
};
var modelTitle = config.defaultModelStructureTitle || 'Browser';
var panelInstance = new _src_gui_ViewerModelStructurePanel__WEBPACK_IMPORTED_MODULE_1__.ViewerModelStructurePanel(_objectSpread(_objectSpread({},
options),
(0, _src_gui_ViewerModelStructurePanel__WEBPACK_IMPORTED_MODULE_1__.generateDefaultViewerHandlerOptions)(this.viewer)),
modelTitle);
this.setModelStructurePanel(panelInstance);
};
The source for ViewerModelStructurePanel shows that it takes the option "startCollapsed" as stated, among other options.
function ViewerModelStructurePanel(viewer, userTitle, ops) {
...
options.startCollapsed = options.startCollapsed !== undefined ? options.startCollapsed : false;

Problem assigning 'doJavaScript' return value to a variable (array)

I'm experimenting with JXA and trying to 'port' a small script, which parses track names from the web page. This script is currently working as Keyboard Maestro macro and is executed in current Safari window:
var trackBlock = document.getElementsByClassName("track tracklist_track_title");
var trackList = [];
for (var a of trackBlock) {
trackList.push(a.innerText);
}
trackList.join("\n");
The problem is that my porting attempt works well in JXA if doJavaScript returns a single string (variable trackName1 contains track title):
var sfr = Application("Safari");
var trackName1 = sfr.doJavaScript('document.getElementsByClassName("track tracklist_track_title")[1].innerText', { in: sfr.windows[0].currentTab });
trackName1 // contains track name
But if I change the code, so that doJavaScript returns an array (as it was in the initial code), the variable is null. Can you, please, explain me: what am I doing wrong?
var sfr = Application("Safari");
var trackBlock = sfr.doJavaScript('document.getElementsByClassName("track tracklist_track_title")', { in: sfr.windows[0].currentTab });
trackBlock[0].innerText; // null
Thank you!
I think the problem is this statement:
trackList.join("\n");
When you put that code in a JXA script, you need to escape the \n:
trackList.join("\\n");
Here's my script that works:
'use strict';
(function myMain() { // function will auto-run when script is executed
var app = Application.currentApplication();
app.includeStandardAdditions = true;
/*
HOW TO USE:
1. Open Safari to this URL:
https://forum.keyboardmaestro.com/
2. Run this script
*/
var jsStr = `
(function myMain2() {
//debugger;
//return 'Just testing';
var elemCol = document.querySelectorAll('div.category-text-title');
var elemArr = Array.from(elemCol);
var titleArr = elemArr.map(e => {return e.innerText});
return titleArr.join('\\n');
})();
`
var safariApp = Application("Safari");
var oTab = safariApp.windows[0].currentTab();
var pageURL = oTab.url();
var pageTitle = oTab.name();
var jsScriptResults = safariApp.doJavaScript(jsStr, {in: oTab})
console.log(jsScriptResults);
return jsScriptResults;
})();
//-->RETURNS:
/* Questions & Suggestions
Macro Library
Plug In Actions
Tips & Tutorials
Wiki
Announcements
Status Menu Icons
Forum Admin
*/
Here is a more clear example of the issue. Here is the code:
var sfr = Application("Safari");
var scr2run = 'document.getElementsByClassName("tracklist_track_title")';
var scr2run1 = 'document.getElementsByClassName("tracklist_track_title")[0]';
var scr2run2 = 'document.getElementsByClassName("tracklist_track_title")[0].innerText';
var trackName = sfr.doJavaScript(scr2run, { in: sfr.windows[0].currentTab });
var trackName1 = sfr.doJavaScript(scr2run1, { in: sfr.windows[0].currentTab });
var trackName2 = sfr.doJavaScript(scr2run2, { in: sfr.windows[0].currentTab });
Here is the output:
app = Application("Safari")
app.doJavaScript("document.getElementsByClassName(\"tracklist_track_title\")", {in:app.windows.at(0).currentTab})
--> null
app.doJavaScript("document.getElementsByClassName(\"tracklist_track_title\")[0]", {in:app.windows.at(0).currentTab})
--> null
app.doJavaScript("document.getElementsByClassName(\"tracklist_track_title\")[0].innerText", {in:app.windows.at(0).currentTab})
--> "From What Is Said To When It's Read"
Why the two first doJavaScript calls return null, but the third one returns expected value?
In answer to your second question:
Why the two first doJavaScript calls return null, but the third one
returns expected value?
var scr2run = 'document.getElementsByClassName("tracklist_track_title")';
var scr2run1 = 'document.getElementsByClassName("tracklist_track_title")[0]';
var scr2run2 = 'document.getElementsByClassName("tracklist_track_title")[0].innerText';
The third JavaScript returns a text value, whereas the first two do not. They return an element collection and an element.

How to make a closed search in Google Docs?

I have a document where I need to find a text or word, each time i run a function the selection has to go to next if a word or text is found. If it is at the end it should take me to top in a circular way just like find option in notepad.
Is there a way to do it?
I know about findText(searchPattern, from) but I do not understand how to use it.
There are several wrappers and classes in the DocumentApp. They help to work with the contents of the file.
Class Range
Class RangeElement
Class RangeBuilder
It is necessary to understand carefully what they are responsible. In your case the code below should be work fine:
function myFunctionDoc() {
// sets the search pattern
var searchPattern = '29';
// works with current document
var document = DocumentApp.getActiveDocument();
// detects selection
var selection = document.getSelection();
if (!selection) {
if (!document.getCursor()) return;
selection = document.setSelection(document.newRange().addElement(document.getCursor().getElement()).build()).getSelection();
}
selection = selection.getRangeElements()[0];
// searches
var currentDocument = findNext(document, searchPattern, selection, function(rangeElement) {
// This is the callback body
var doc = this;
var rangeBuilder = doc.newRange();
if (rangeElement) {
rangeBuilder.addElement(rangeElement.getElement());
} else {
rangeBuilder.addElement(doc.getBody().asText(), 0, 0);
}
return doc.setSelection(rangeBuilder.build());
}.bind(document));
}
// the search engine is implemented on body.findText
function findNext(document, searchPattern, from, callback) {
var body = document.getBody();
var rangeElement = body.findText(searchPattern, from);
return callback(rangeElement);
}
It looks for the pattern. If body.findText returns undefined then it sets on top of the document.
I have a gist about the subject https://gist.github.com/oshliaer/d468759b3587cfb424348fa722765187

How to retrieve the data from database using Indexed DB

I have an existed database. I'm trying to retrieve the data from database using indexedDB but i'm unable to get the data from database.
var data = [];
// creating or opening the database
var db;
var request = window.indexedDB.open("database");
request.onerror = function(event) {
console.log("error: ");
};
request.onsuccess = function(event) {
db = request.result;
console.log("success: "+ db);
};
request.onupgradeneeded = function(event) {
var db = event.target.result;
var objectStore = db.createObjectStore("Subject", {keyPath: "id"});
for (var i in data) {
objectStore.add(data[i]);
}
}
function readAll() {
var objectStore = db.transaction("Subject").objectStore("Subject");
console.log(objectStore);
objectStore.openCursor().onsuccess = function(event) {
var cursor = event.target.result;
if (cursor) {
alert("Name for id " + cursor.key + " is " + cursor.value.Subject);
cursor.continue();
}
else {
alert("No more entries!");
}
};
}
Thanks in Advance.
You're pretty close.
var data = [];
I'll presume that you actually have some data somewhere, and that it indeed has an id attribute since you're specifying that as your index key e.g.
var data = [{id: 'foo' }, { id: 'bar' } ];
Now here:
var objectStore = db.createObjectStore("Subject", {keyPath: "id"});
for (var i in data) {
objectStore.add(data[i]);
}
(Careful with for..in and arrays)
I don't think you're actually adding any data here, which is one reason why you can't read it. To add data to an object store, try to first create a read/write transaction first and then get your reference to the object store and add your object.
var trans = db.transaction(["Subject"], "readwrite").objectStore("Subject");
Note the usage of an array as the first argument to transaction() and "readwrite" as the second param. (Some examples use the IDBTransaction.READ_WRITE constant but this doesn't seem to work with recent versions of Webkit.)
var objectStore = db.transaction("Subject").objectStore("Subject");
Try this instead:
var trans = db.transaction( [ "Subject" ] );
, objectStore = trans.objectStore( "Subject" );
objectStore.openCursor( IDBKeyRange.lowerBound(0) ).onsuccess = function(event) {..}
I did encountered the same error once. it occurs because at times the onSuccess is executed even before the result data is returned. So you should check if result data is empty.
To solve the issue try using oncomplete instead of onSuccess and also use Jquery indexedDB plugin. The plugin requires certin code changes but has more consistent implementation of indexedDB.
See http://nparashuram.com/jquery-indexeddb/

google script unable to update web page

Below is snippet where I create a web page / app and show a list. Then on click of item in list I want to replace the list with another list. I try it by using vertical panel as root container and clearing and adding list to it. The web page though keeps showing old list even after handler for new list executes fine.
//user_list function updates vertical panel but the web page still shows old rep_list
function doGet(e) {
if(e == null || e.parameter.dvid == null) {
return rep_list();
}
}
function user_list(path) {
var app = UiApp.getActiveApplication().setTitle("List Folders");
var content = app.getElementById('root');
content.clear();
var list = app.createListBox();
//populate list
content.add(list);
return app;
}
function rep_list () {
var app = UiApp.createApplication().setTitle("List Repositories");
var content = app.createVerticalPanel().setId('root');
var list = app.createListBox(true).setId('repoList').setName('repo');
var handler = app.createServerHandler("listFolders");
list.addClickHandler(handler)
//populate list
content.add(list);
app.add(content);
return app;
}
function listFolders(e){
var repo = e.parameter.repo;
user_list(repo);
}
Regards,
Miten.
Your listFolders() function isn't returning a new UI instance. Try:
function listFolders(e){
var repo = e.parameter.repo;
var app = user_list(repo);
return app;
}