HOME THOUGHTS ABOUT CONTACT

jsscribe.com

some thoughts about JavaScript and other things...

thoughts...


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.

script dictionary

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.

script dictionary

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.

script dictionary

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.

View the complete script on GitHub

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:

About Me


Hi, I'm Bruce. I do web development work in San Jose, California. You can email me at bruce at the domain name jsscribe.com or send me a message below.


Send me a message:

 

copyright 2024, jsscribe.com