JavaScript for OSX Automation, or JXA for short, is Apple's not so well known
and not so popular JavaScript implementation of a scripting language for MacOS. It gives us
the ability to run scripts that can interact with the OS and programs
in a framework that is more powerful than terminal scripts and less complicated
(and less powerful) than compiled OSX programs. JXA scripts can be created, compiled, and of course
run in the Apple Script Editor that comes preinstalled on MacOS. And it appears to have been conceived
as an addition to AppleScript in the same way as JScript was a an addition to VBScript in MS Windows.
Few today use JXA; and some, if they've looked into it all, are even confused by
the platform. That most don't consider it useful, if they are even aware of it, shows that JXA
was not a success for Apple. JXA has little support from Apple, and documentation for the platform is
difficult if not impossible to find.
I believe the two hurdles in learning and using JXA are (1) reading and understanding the Script Dictionary
Viewer,
and (2) understanding that JXA object references are different from ordinary JavaScript object references.
As an exercise, I've created a JXA script that creates a text document that lists all the contacts
in my local address book containing the names, addresses, and phone numbers of the contacts and saves
this text document to the desktop. The full script is located on GitHub. What follows are two
sticking points I've encountered while learning to use JXA. Click on the image to learn more.
First and foremost a disclaimer: JXA, as I've said before is not well documented. In learning to work with
the framework,
I've had to scour the web for examples and tips, and many times had to resort to trial and error.
I've listed some of the excellent resources I found below. There are probably
better ways to code this script, but it's hard to find other examples at this point. If there
are any JXA experts out there, I'd love to hear from them. With that said, what follows is my best guess
about getting started and working with the Apple Script Editor and JXA.
The First Hurdle
The first part of understanding how to use JXA is to be able to read and understand the layout of the
Script Dictionary Viewer. Looking at the script definition page for the Contacts app, we see three
sections, or panes of info. The first of the top three panes contains what are called "Suites".
Each suite contains classes of objects that we can use to access info about our contacts.
Upon highlighting a suite in the top left pane, we see that the middle pane populates with
a list of classes that are available in the suite (they have the purple square
icon with a "C" in the middle).
These lists (middle and right)
don't tell us how
to use these classes in relation to one another. They simply list possible classes for us to use, one after
the other (not very user friendly). The key to understanding these three panes (overcoming the first hurdle)
lies
in examining the pane that is under these three: this is where the class definitions are.
We begin by highlighting the Address Book Script Suite. The first class we are going to use is a function
called Application().
We see this function in the middle top pane. We create a reference
to the contacts app by giving Application function the argument "contacts". We get back a reference to the
contacts application
that we can use as a top level object that contains other objects.
let contactsApp = Application("contacts");
For our exercise, we need the script to generate a list of persons (our contacts) and info
about each. We know that Application is our top level object from reading the class description in the lower
pane.
Highlighting the
Application class entry in the middle pane also shows us that it contains two elements (or collections):
"person" and "group".
These two elements
are implemented here as other objects. We aren't concerned with "group" here,
only "person". The key here is to understand that the class description in the lower dictionary pane is
where
we learn HOW to properly construct our code to access the classes listed in the top panes. Without this
description in the lower pane,
the dictionary really
doesn't help us create scripts.
Again, the highlighted Application reference contains an element called "person"
(left top pane).
But in our code, we need to access a collection of multiple "person" elements. It is ONLY in the
description (lower pane) that we learn we can use "people" to refer to a collection of "person" elements.
We use the PLURAL of this element name (person) shown in the top right pane
in our code. Again, this is only clear from reading the description.
let objSpecPeople = contactsApp.person(); <<-- will not work
let objSpecPeople = contactsApp.people(); <<-- WORKS!
The Second Hurdle
The other part of writing JXA automation scripts (the second hurdle) is to know what kind of objects we are
dealing with.
JXA seems to contain two kinds of objects: regular JavaScript objects and objects particular to JXA called
Object Specifiers. Object Specifiers can't be treated as normal objects on there own, but first need to be
converted to regular JavaScript objects before we can apply regular operations to them. In order to
differentiate
between the two kinds, we can use the .toString.call()
method in some cases. Using this
method
can tell us if the reference points to a JavaScript Object or an Object Specifier. We can convert a
specifier to a
regular JavaScript object by invoking it as a function: by putting parentheses on the end of the correct property of the
specifier.
For example, we need to get a collection of people objects from the contact application object. The code is
as follows:
// first we get the contact application reference
let contactsApp = Application("Contacts");
// next we get the array specifier reference for the people in our contacts
let objSpecPeople = contactsApp.people;
// we know that what we have is an array specifier from the .toString.call() method.
console.log(Object.prototype.toString.call(objSpecPeople);
// the console message shows /* [object ArraySpecifier] */ <-- obj is array specifier
// invoking the ArraySpecifier as a function gives us a JavaScript array
let jsArrayOfPeople = objSpecPeople();
console.log (jsArrayOfPeople.length); // gives number of people in contacts
console.log(Object.prototype.toString.call(jsArrayOfPeople);
// the console message shows /* [object Array] */ <-- has changed from array specifier to array
// but can we use this array? what does this array contain?
// accessing the first element of the array should tell us what we have
console.log(Object.prototype.toString.call(jsArrayOfPeople[0]);
// the console message shows /* [object ObjectSpecifier] */ <-- array contains object specifiers!
Looking at the above code, we see a pattern throughout JXA. We create references to object
specifiers and then convert those specifiers to regular JavaScript objects — on which we
perform normal operations. In the code above we see that invoking certain object specifiers
as functions returns references to normal JavaScript objects. The line above
let jsArrayOfPeople = objSpecPeople();
does just this. The array object returned
contains a collection of object specifiers, each containing the reference to one person
in our contacts app. Each person object contains properties such as name, telephone numbers, and
addresses for the contact. Taking just the first person object as an example, we can get the
name of this contact by invoking the .name
property as a function, as is shown below.
// get the name of one person by invoking the name property of the object specifier of that
// person as a function
let personName = objSpecPeople[0].name();
console.log(Object.prototype.toString.call(personName));
// the console message shows /* [object String] */ <-- type of object is string
console.log(personName);
// the console message shows /* John Doe */ <-- the name of the contact as it appears
// in the contacts app is shown in the console, which is just what we want
The main operation of the script then is to get a collection of people specifiers (each
referring to one person), and then to convert this info to standard JavaScript objects
which the script will manipulate into a usable format for output.
In our exercise, the output will be a standard text file that will be saved on the desktop.
The text file will contain a list of all our contacts with just the info that we
specify. Though each person object in our contacts can contain multiple properties,
we are only outputting a few: their names, numbers and addresses. Properties like "birthday" or
"job tile", though possibly listed as info for the contact, will not be
in our output (we could of course include this if we wanted).
To summarize then, the first hurdle is reading and understanding the object hierarchy in the
Scripting Dictionary. The understanding of the hierarchy comes from reading the lower
description pane in the Scripting Dictionary, and NOT by looking at the object relationship info
on the top three panes of the dictionary (unintuitive as this may be). Once we see which objects
we need, the second hurdle is to convert the objects that are
specifiers into standard JavaScript objects by invoking properties of these objects as
functions. Once we have converted specifiers into regular objects, we are able to format
and output this info using standard JavaScript operations.
I've uploaded the full script code to my GitHub repository for use by anyone who's interested.
Also, I have to give credit to the websites that helped me understand some of what is
going on with the JXA platform. The sites below provide great resources for JXA info: