The first step in working with an app is to get an instance
of it like so: const app = Application("AppName")
. This is
the easiest method. You could also use the bundle
identifier, the path to the app’s bundle on disk or the
process ID of a running app. Most of those are not very
practical, so we’ll stick with the method above.
Get an app’s properties and elements#
As said before, the primary
source for information on an app’s properties and elements
is its function library that you can inspect in the script
editor. However, there are some methods that allow you to
retrieve very basic information from the application with
JavaScript itself. Unfortunately, the usual JavaScript
suspects like getOwnProperties
do not work.
For Apple’s Finder app, you could for example do this
(() => {
const app = Application("Finder");
const elements = app.properties();
for (e in elements) {
const v = e ?
`${e}: ${Automation.getDisplayString(elements[e])}`
: "undefined";
console.log(v);
}
})()
Note the usage of Automation.getDisplayString()
here in
line 6. It
converts the property values to something that is not only
legible but also valuable, because it tells you a lot about
the internal workings of JXA (see the complete output of the
script below).
Output of
properties
for Finder app
class: undefined
name: "Finder"
clipboard: null
visible: true
frontmost: false
selection: []
insertionLocation: Application("Finder").startupDisk.folders.byName("Users").folders.byName("xxx").folders.byName("xxx").folders.byName("xxx").folders.byName("jxa").folders.byName("content")
productVersion: ""
version: "11.5"
desktop: Application("Finder").desktop
desktopPicture: Application("Finder").startupDisk.folders.byName("Users").folders.byName("xxx").folders.byName("Pictures").documentFiles.byName("Fotos-Mediathek.photoslibrary").folders.byName("originals").folders.byName("9").documentFiles.byName("938AFCCA-70EF-4C00-9065-97C553AB460F.heic")
finderPreferences: Application("Finder").finderPreferences
startupDisk: Application("Finder").startupDisk
trash: Application("Finder").trash
home: Application("Finder").startupDisk.folders.byName("Users").folders.byName("xxx")
JXA scripting in Mail is partially broken. Notably, the access to Mail’s properties has been butchered by Apple. Therefore, the examples here use Finder.
Alternatively, you could use propertiesOfClass()
like
so:
(() => {
const app = Application("Finder");
const elements = app.propertiesOfClass("application");
elements.forEach( el => console.log(el));
})()
However, getPropertiesOfClass()
will only tell you the
names of the properties, whereas properties()
gives you
an object with the property names as attributes and the property
values as their values.
A similar function to get at the elements of an application
is elementsOfClass
. This will tell you about all elements,
i.e. child objects. These elements are always lists:
(() => {
const app = Application("Finder");
const elements = app.elementsOfClass("application");
elements.sort().forEach( e => {
console.log(`${e} has ${app[e]().length} elements`);
})
})()
As shown above, you can also access properties with
JavaScript’s usual square bracket syntax like app[property]()
.
Output of elementsOfClass
for Finder
aliasFiles has 1 elements
applicationFiles has 4 elements
clippingWindows has 0 elements
clippings has 0 elements
containers has 5 elements
disks has 22 elements
documentFiles has 3 elements
files has 8 elements
finderWindows has 1 elements
folders has 5 elements
internetLocationFiles has 1 elements
items has 13 elements
packages has 4 elements
windows has 1 elements
Getting types of properties and more#
To get the type of a property, use
propertyTypeForNameInClass()
like so
(() => {
const app = Application("Finder");
const props = app.propertiesOfClass("application");
props.forEach( p => {
let t = app.propertyTypeForNameInClass(p);
console.log(`${p} is of type "${t}"`);
});
})()
When you run this script, you’ll see that the type of some
properties (for example productVersion
) is undefined
although it obviously is of type text
if you look at its
value with properties()
. This is one of the consequences
of Apple’s half-baked implementation of JXA. Most of the
times it works, sometimes there a workarounds and sometimes
it is just so broken that you can’t do anything about it.
In order to get the parameters of a command (method) and
their type, you can use parametersForCommand()
and
parameterTypeForNameInCommand()
. For example, to
determine the parameters for the Finder’s make
command,
you’d use
(() => {
const app = Application("Finder");
const params = app.parameterNamesForCommand("make");
params.forEach( p => {
let t = app.parameterTypeForNameInCommand(p);
console.log(`${p} is of type "${t}"`);
});
})()
Running this script unfortunately gives only unsatisfying output:
at is of type "undefined"
new is of type "undefined"
to is of type "undefined"
withProperties is of type "undefined"
So it is possible to get some information about the internals of applications, their classes, methods and properties. But this does not work in all cases, and the only sufficiently reliable source of information is the Script Editor.
Creating new objects#
Some applications offer methods to create new objects, e.g. Mail and Notes. The
scripting dictionaries for these apps seem to indicate that new objects should
be created by calling the make
method. However, this does not work in JXA.
To create a new top level document in an app, you have to use the documents class name
as a method on the application object and then make()
on the
returned object. For Apple’s Numbers spreadsheet app, it looks like this:
(() => {
const app = Application("Numbers");
const doc = app.Document().make();
})()
You can pass parameters to the newly created object by adding them to the Document
call like so:
(() => {
const app = Application("Numbers");
const myTemplate = app.templates["My Template"];
const doc = app.Document({documentTemplate: myTemplate}).make();
})()
This works for top level documents in an app, i.e. Document
in Number and
Pages, Note
in Notes and so on. Adding an object to a top level document does
not require a call to make
but only to the class-named method. To add a
new sheet to the first Numbers document, you’d use:
(() => {
const app = Application("Numbers");
const doc = app.documents[0];
const sheet = app.Sheet({name: "My Sheet"});
doc.sheets.push(sheet);
})()
Since a Document
contains sheets
, you push a newly created sheet onto this
list with push
. Note, however, that many other array methods do not work
in this case. For example, you cannot pop
an element from sheets
.