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

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.

Related

How to build dynamic dropdowns in configuration setup?

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.

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;

How to populate a HTML page with Firebase data?

I am trying to populate a page with firebase data.
This is my firebase data structure...
What I want is to create number of divs according to the number of posts in firebase. And in the divs with title and subtitle in h2 tag and p tag.
I am new to firebase soo any help would be appreciated...
and also i want to limit the number of divs to 4 starting from the latest post.
this is my java script
firebase.initializeApp(firebaseConfig);
var postsRef = firebase.database().ref("posts").orderByKey();
postsRef.once("value").then(function (snapshot) {
snapshot.forEach(function (childSnapshot) {
var key = childSnapshot.key;
var childData = childSnapshot.val();
var name_val = childSnapshot.val().title;
var id_val = childSnapshot.val().subtitle;
console.log(name_val);
var post = document.getElementById('#tst-post');
var divh2 = document.createElement('h2');
divh2.innerText - childData.val().title + "---" + JSON.stringify(childData.val());
$(post).append(divh2);
});
});
i dont know what i am doing in this code, I just watched some tutorials. Please help me.
You are not very far from a result.
By searching on the internet (https://www.google.com/search?client=firefox-b-d&q=how+to+dynamically+create+div+in+javascript) you can easily find a lot of examples on how to create DIVs dynamically. For example: https://stackoverflow.com/a/50950179/3371862
Then, in the Firebase Realtime Database documentation you find how to filter data and in particular how to "Sets the maximum number of items to return from the end of the ordered list of results" with limitToLast().
So if you put all of that together as follows, it should do the trick:
<script>
var postsRef = firebase
.database()
.ref('posts')
.orderByKey()
.limitToLast(4);
postsRef.once('value').then(function(snapshot) {
snapshot.forEach(function(childSnapshot) {
var key = childSnapshot.key;
var childData = childSnapshot.val();
var name_val = childSnapshot.val().title;
var id_val = childSnapshot.val().subtitle;
createDiv(name_val, id_val);
});
});
function createDiv(title, subtitle) {
var myDiv = document.createElement('DIV'); // Create a <div> node
var myTitle = document.createTextNode(title); // Create a text node
myDiv.appendChild(myTitle); // Append the text
var mySubtitle = document.createTextNode(subtitle); // Create a text node
myDiv.appendChild(mySubtitle); // Append the text
myDiv.style.backgroundColor = 'grey';
myDiv.style.border = 'solid';
myDiv.style.margin = '10px';
document.body.appendChild(myDiv);
}
</script>

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

Reference Google Spreadsheet (CSV) in Jekyll Data

I am managing a website displaying a lot of tabular data (language stuff) and running on Jekyll. I really like to display content based on a CSV file stored in the _data folder of Jekyll.
I would like to be able to edit / add / remove content from this CSV directly on Google and then reference it to Jekyll (like a shortcut or something that sync the CSV content from Google to my static folder).
Which way would be the simplest to reference an external file (either in the _data folder or directly in my templace). I can find the CSV file with this kind of link but downloading it every time is a hassle (https://docs.google.com/spreadsheets/d//export?format=csv).
How can Jekyll understand data from external stored file (maybe in javascript ?).
Thank you.
Getting datas from google docs is becoming harder ;-(
I've tried with jquery.ajax but I met the CORS limitation.
Then I found tabletop and it works !
go to your google spreadsheet and File > Publish to the web > Start publishing
note the publish url
download tabletop script and save it to eg: js/tabletop.js
put a link at the bottom of your _includes/header.html eg
<script src="`{{ site.baseurl }}`/js/tabletop.js"></script>
in a data.html page put
---
title: csv to json
layout: page
---
<div id="csvDatas"></div>
you can now get your datas with a js/script.js file that you've also included at the very end of you _includes/footer.html
var csvParse = function() {
// put you document url here
var sharedDocUrl = 'https://docs.google.com/spreadsheets/d/1Rk9RMD6mcH-jPA321lFTKmZsHebIkeHx0tTU0TWQYE8/pubhtml'
// can also be only the ID
// var sharedDocUrl = '1Rk9RMD6mcH-jPA321lFTKmZsHebIkeHx0tTU0TWQYE8'
var targetDiv = 'csvDatas';
// holds datas at a closure level
// this then can be accessed by closure's functions
var dataObj;
function showInfo(data, tabletop) {
dataObj = data;
var table = generateTable();
var target = document.getElementById(targetDiv);
target.appendChild(table);
}
function generateTable(){
var table = document.createElement("table");
var head = generateTableHeader();
table.appendChild(head);
var body = generateTableBody();
table.appendChild(body);
return table;
}
function generateTableHeader(){
var d = dataObj[0];
var tHead = document.createElement("thead");
var colHeader = [];
$.each(d, function( index, value){
console.log(index + ' : ' + value);
colHeader.push(index);
});
var row = generateRow(colHeader, 'th');
tHead.appendChild(row);
return tHead;
}
// this can be factorized with generateTableHeader
function generateTableBody(){
var tBody = document.createElement("tbody");
$.each(dataObj, function( index, value ){
var rowVals = [];
$.each(value, function(colnum, colval){
rowVals.push(colval);
});
var row = generateRow(rowVals);
tBody.appendChild(row);
});
return tBody;
}
function generateRow(headersArray, cellTag){
cellTag = typeof cellTag !== 'undefined' ? cellTag : 'td';
var row = document.createElement("tr");
$.each(headersArray, function( index, value){
if( value != "rowNumber"){
var cell = document.createElement(cellTag);
var cellText = document.createTextNode(value);
cell.appendChild(cellText);
row.appendChild(cell);
}
});
return row;
}
return {
init: function() {
if( $('#' + targetDiv).length ){
Tabletop.init( { key: sharedDocUrl ,
callback: showInfo,
simpleSheet: true } );
}else{
console.log('Not the good page to parse csv datas');
}
}
};
}();
$( document ).ready(function() {
csvParse.init();
});