A Faint Outline

through the mists of time

Colin Fallon
10 min readJan 14, 2018

A little behind the times I know but Happy 2018 to all of you. I hope you had a great break over the festive period. I was lucky enough to have nearly two weeks away from work — some welcome time to do the things you never get a chance to do with a busy career in IT.

Yes, a perfect opportunity to fire up my WebStorm and cut some code!

Should auld acquaintance

New Year is a time for looking back, remembering old friends and those you have loved. This year my thoughts were with Userland Frontier — fine memories indeed of many (and I mean many!) hours spent developing web-sites through the dotcom bubble.

Frontier was both an automation tool, allowing you to glue together various applications on your Mac, and a web development tool. It had it’s own programming language, UserTalk — it was a bit of a crazy language but for me infinitely preferable to the much crazier AppleScript that was Apple’s automation solution.

And what is it with AppleScript? — this was a programming language supposed to be readily accessible by the end user, and yet as a professional software developer I have never been able to get my head round it!! Could be something to do with my head I suppose.

Ahead of its time?

It was as a web development tool that Frontier really shone — after all there’s only so much desktop automation you can do and still be left with a life. In many respects it was ahead of it’s time — back in 2000 it could host dynamic blog sites where the author created and edited their blog in the browser.

But for me it was the tools that Frontier provided to generate static web-sites from simple outlines that you authored in Frontier that really shone. You could easily generate headings, button bars, next/prev links and the rest with a set of simple (well, reasonably simple) macros. Once you had set up the framework for a website, then generating new sections and pages was “easy” and all you had to think about was the content.

Static websites seemed to fall out of fashion for a while — the world was a dynamic place — but recently they been very much back in vogue. There are a plethora of tools to choose from with the popularity of tools like Jekyll, Docpad and Hugo showing that Frontier was ahead of its time — one of the reasons no doubt that it died a death! While AppleScript lives on!!

Overdose

Ok, sorry about the nostalgia overdose and thanks for bearing with me. As a failed AppleScript programmer, when Apple announced JavaScript for Automation(JXA) in 2014 my interest was piqued. JavaScript is my language of choice, Mac my platform of choice — a thrilling combination, so thrilling that just 3 years later I am taking a look at it! And what better way to start than with an outline.

Take a look at this Frontier help page dating back to 2001. Scroll down to the first image of a Frontier outliner with an extract from the US Constitution. And then take a look at the image of the OmniOutliner document below.

Do you see what I’ve done there?

Anyone for more nostalgia?

OmniOutliner is another great tool that I’ve been using since the year dot. All those years working in Frontier mean that I tend to think in outlines. With the demise of Frontier, I needed to keep some semblance of an outline in my life — and OmniOutliner was that source of comfort.

Omni Group are great supporters of the Mac, and their desktop tools are perfect Mac citizens and always scriptable. Details of their current support for Mac automation show that they support both AppleScript and JavaScript for automation (provided you have the more expensive pro edition I’m afraid). And so my Christmas fun was decided.

In with the new

Omni’s automation appears to be targeted at running scripts from within the Outliner itself within its own console, and access to external packages via npm looks challenging. I was looking to work within the nodejs environment that I am familiar with and so have used Sindre Sorhus’s run-jxa package to run jxa code and get the result.

I probably should have anticipated it but my first discovery was that all inter application communication in macOS is asynchronous, so I decided to bite the bullet and use the ES8 async/await pattern for the first time. Never mix the old and the new I hear you say — too late!

First foot

To follow along with the code in this article, you can clone the project from GitHub. Follow the instruction in the Readme file to set it up.

The first thing you need to know is that each Mac application that supports JXA publishes a dictionary defining the methods and properties that it supports. This is all implemented as a bolt-on to the original AppleScript approach and so to access the JXA dictionary for an application you use the Script Editor (if you’ve never used it before it’s in the Utilities folder of your Applications folder). Launch Script Editor, select File -> Open Dictionary… from the menu and choose the application you are interested in. For our first example let’s start with iTunes.

Open the iTunes dictionary from Script Editor and select JavaScript from the drop-down in the toolbar instead of AppleScript, which is the default. If you click on iTunes Suite in the left side panel you will see the list of iTunes specific JXA properties etc. Scroll down in the middle panel and select Application — in the right side panel you can now see everything provided by the Application object.

One of the iTunes Application properties is currentTrack. If you select it in the right side panel, it will be highlighted below — where you will see that it an instance of a Track object. Click on the word Track that displays as a link and you will now be looking at the properties for the Track object, one of which is artist.

It’s not exactly a paragon of Apple’s ease of use but it serves its purpose and we now know that the artist for the current track playing in iTunes is at Application.currentTrack.artist.

Code Structure

Last thing before we dive into accessing iTunes via JXA is that I need to explain the structure that all the code examples in this article follow. The diagram below sums it all up, and nicely introduces the use of ES8 async/await.

The getXXX function contains an asynchronous call, and this is denoted using the async keyword. The await keyword then marks the actual asynchronous call that needs to be “waited for” within the async function .

Functions marked as async always return a Promise and you can see how I am dealing with this in the part of the code marked as Top level code.

Example 1 — iTunes current track

Ok, so let’s look at the code to retrieve the artist if the current track playing in iTunes. It’s file ex1.js in the project and is the same code as in the structure image in the previous section above.

Start iTunes and play your favourite music of the moment. Then from the command line in the project directory execute the command node ex1 and you should see output along the lines of

{ artist: 'Hurray for the Riff Raff' }

The actual JXA code in the readArtist function is trivial and fairly intuitive given that we know from the iTunes dictionary that the information we are looking for is at Application.currentTrack.artist

let iTunes = Application('iTunes')
let artist = iTunes.currentTrack.artist()

The only trick here is that iTunes.currentTrack.artist actually contains a reference to the required data - and the way to get the value of the data is to invoke the required property as a function. Hence the expression iTunes.currentTrack.artist()

Example 2 — Outline top level

OK, so now lets look at OmniOutliner and see if we can read the top level rows from the outline. Open the project file tutorial.myOutline.ooutline in OmniOutliner (the pro version) and then look at the code in the file ex2.js.

The JXA code in the readRows function is a little more complex than the first example and took a bit of trial and error. The main cause of error was my confusion around the way that JXA returns object specifier references for data in an array. This is described in the Apple docs but it took me several attempts to properly digest it.

In my example I have a variable rows that contains a reference to an array of top level rows of the outline. I want to retrieve the name attribute of each of the rows in the array and to do this I need use the code rows.name() to dereference the name property of all rows - so it returns an array containing a list of the values of the name attribute for each row. A little bit tricky but easy when you know how!

So if you now run node ex2 you should see something similar to the following output:

[ { id: 'm6Nf2Le-Mz7',
name: '#title "My Outline"',
hasChildren: false },
{ id: 'jKBDwOeYIAf',
name: '#renderOutlineWith "fatHeadlines"',
hasChildren: false },
{ id: 'kxfWD6_gBqo',
name: 'Freedom from Unreasonable Search and Seizure',
hasChildren: true },
{ id: 'hPvXPOJh_n2',
name: 'Freedom to Bear Arms',
hasChildren: true },
{ id: 'klrFWik6fhi',
name: 'Freedom of Speech',
hasChildren: true },
{ id: 'f9Xx92rNlUs',
name: 'Freedom from Unlawful Quartering',
hasChildren: true } ]

Taking directives

The first two rows in the outline are actually what Frontier called directives. They are essentially metadata rather than part of the actual outline itself and so need to be separated from the data and then parsed into the directive and its value — so, for example the value of the title directive is My Outline.

The file ex3.js contains a few additional lines to do this. It’s just a bit of JavaScript parsing, so you can check it out at your leisure and run node ex3 to validate that you can now separate the directives out as per the following output

{ title: 'My Outline', renderOutlineWith: 'fatHeadlines' }

The final awaits

Ok, well done for getting this far!! Now it’s time to put it all together for the final version which you can find in the file ooToJs.js

We have only read the top level of the outline in our examples so far, but obviously this is somewhat useless since the whole point of an outline is its nested hierarchy. You may have noticed that I did return a hasChildren boolean for each outline row to indicate that there were children for that row that needed to be retrieved.

This is a classic case for a recursive function. Each time the function reads a row where hasChildren is true the function invokes itself to read the children and so on down the hierarchy. Fairly straightforward but how to do this with our ES6 async/await approach?

Here’s my function to read the children of an outline row

You can see that it’s now much more liberally sprinkled with the await keyword and there are now three separate points where asynchronous code is executed. One of these, the actual recursive code to call getChildren within getChildren, is actually in an anonymous function passed to the map function - this anonymous function must also be marked as an async function hence the async child within the map arguments.

Believe me this took a few attempts and I also had to introduce await Promise.all to deal with the all the Promises produced by the map function. It’s only a few lines of code but I was quite smugly satisfied when it worked!

So now all that remains is to run node ooToJs and marvel as we see our outline in a nice JavaScript object. Time to think about how to render it to html…

{ directives: { title: 'My Outline', renderOutlineWith: 'fatHeadlines' },
outline:
[ { id: 'kxfWD6_gBqo',
name: 'Freedom from Unreasonable Search and Seizure',
children:
[ { id: 'bQG6KJRTIxA',
name: 'The right of the people to be secure in their persons, houses, papers, and effects, against unreasonable searches and seizures, shall not be violated; and no Warrants shall issue but upon probable cause, supported by Oath or affirmation, and particularly describing the place to be searched, and the persons or things to be seized.' } ] },
{ id: 'hPvXPOJh_n2',
name: 'Freedom to Bear Arms',
children:
[ { id: 'coCClU5Exu_',
name: 'A well regulated Militia, being necessary to the security of a free State, the right of the people to keep and bear Arms, shall not be infringed.' } ] },
{ id: 'klrFWik6fhi',
name: 'Freedom of Speech',
children:
[ { id: 'hZZrpAy_rGt',
name: 'Congress shall make no law respecting an establishment of religion, or prohibiting the free exercise thereof; or abridging the freedom of speech, or of the press; or the right of the people peaceably to assemble, and to petition the Government for a redress of grievances.' } ] },
{ id: 'f9Xx92rNlUs',
name: 'Freedom from Unlawful Quartering',
children:
[ { id: 'cIZaqZYlNjN',
name: 'No Soldier shall, in time of peace be quartered in any house, without the consent of the Owner, nor in time of war, but in a manner to be prescribed by law.' } ] } ] }

--

--