This section explains some of the basic building blocks of JXA scripts. Other parts of this site will use them regularly.
Self-executing function#
The basic structure for JXA script on this site is a
self-executing anonymous function. It has the advantage that
it can be employed in the Script Editor as well as with
osascript
to run automatically. It looks like
this:
(() => {
// script code goes here
})()
Using this construct permits you to use constants within
your script that can be overwritten in Script Editor on each execution
without raising an error. Also, you can use return
to exit
from the script.
Get the application instance#
Most methods are bound to the instance of an application (this might not be completely correct, but it is accurate enough). So to use a method of an application, you first need the application’s instance, like so:
const app = application("theApplicationName");
For example,
const app = application("Mail");
will give you the application instance for Apple’s Mail app
in the constant app
.
You should useconst
whenever possible to avoid accidentally changing an object. If you wrap your script in a self-executing anonymous function like described before,const
declarations will work just fine for multiple executions. Otherwise, you’ll have problems because running the script a second time will produce an error since the it attempts to change aconst
.
Enable dialogs#
Sometimes, you might want to ask the user of a script for input or alert them to some problem. For that, you need different dialogs. All of them are only available if you add the following line to your script:
app.includeStandardAdditions = true;
Handling errors with try … catch#
Error handling is necessary, and one way to do that is using
try…catch
blocks. JXA does not differ from vanilla JavaScript in
this aspect. However, some of the user interaction methods throw an error when
the user cancels the dialog. You might want to catch that and handle it
differently from other errors.
An example that ignores cancellation by the user and displays all other error messages might look like this:
try {
... some code ...
} catch (e) {
if (e.errorNumber === -128)
/* User canceled the dialog. Do nothing */
return;
} else {
/* something else happened, show an alert */
app.displayAlert(e.message,
{ message: "An error occured",
as: "critical"
button: "OK"
})
}
}
The object passed to catch
is defined as
{
column: integer,
line: integer,
errorNumber: integer,
message: string,
stack: string,
sourceURL: string,
}
line
and column
refer to the location of the error in the file sourceURL
.
errorNumber
is one of the numbers defined by Apple for its scripting
architecture, message
is the localized error message, and stack
gives you the
call stack at the time when the error occurred.
Since the error message is localized, it is ok to display it to the user, but
not useful to check if you want to react differently to different error
conditions. For that purpose, refer to the errorNumber
instead.
Prepare for reading and writing files#
If you want to read from or write to a file, you have to work with the “current application”. All file operations only work on this object that you get something like this:
const curApp = Application.currentApplication();
curApp.includeStandardAdditions = true;
Using shell commands#
Using the “current application” (see above) is also necessary if you want to execute shell commands
with doShellScript()
: curApp.doShellScript("…")
works
whereas app.doShellScript()
for an arbitrary application
does not. To pass file names to shell commands, you should
always include them in single quotes (’) and use a template
string, for example
const curApp = Application.currentApplication();
curApp.includeStandardAdditions = true;
const filename = "/users/me/Path to Folder/My File.txt";
curApp.doShellScript(`wc -l '${filename}'`);
The single quotes around file names make sure that the shell
does not interpret any special characters contained in them
and escapes spaces. However, if the parameters to your shell command can
contain single quotes (apostrophes), you must escape those as well, for example
using parameter.replaceAll(/'/g,"'\''")
.
Writing and reading Unicode data#
In many cases, the JXA functions to write data to files are not sufficient. They only deal with ASCII text, and it is impossible to change that. If you want to write Unicode data, you have to use the “Objective-C bridge” like so:
ObjC.import('Foundation');
const filename = "/users/me/Path to Folder/My File.txt";
const str = $.NSString.alloc.initWithUTF8String('Unicode Text 广州');
str.writeToFileAtomicallyEncodingError(filename,
true, $.NSUTF8StringEncoding, null);
In this case, there is no need to open the file for writing or close it again.
Inversely, to read Unicode data, you’d use
ObjC.import("Foundation");
const fm = $.NSFileManager.defaultManager
const data = fm.contentsAtPath(filename)
const str = $.NSString.alloc.initWithDataEncoding(
data, $.NSUTF8StringEncoding)