JXA Basics

JXA Basics

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:

1
2
3
  (() => {
    // script code goes here
  })()

Using this construct permits you to use constants within your script that can be overwritten 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:

1
  const app = application("theApplicationName");

For example,

1
  const app = application("Mail");

will give you the application instance for Apple’s Mail app in the constant app.

You should use const 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 a const.

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:

1
  app.includeStandardAdditions = true;

Handling errors with trycatch #

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
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

1
2
3
4
5
6
7
8
{
  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:

1
2
  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

1
2
3
4
  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.

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:

1
2
3
4
5
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

1
2
3
4
5
ObjC.import("Foundation");
const fm = $.NSFileManager.defaultManager
const data = fm.contentsAtPath(filename)
const str = $.NSString.alloc.initWithDataEncoding(
    data, $.NSUTF8StringEncoding)
About