Making A Mac App Scriptable Tutorial

Allow users to write scripts to control your OS X app – giving it unprecedented usability. Discover how in this “Making a Mac App Scriptable Tutorial”. By Sarah Reichelt.

5 (1) · 1 Review

Save for later
Share
You are currently viewing page 3 of 3 of this article. Click here to view the first page.

Adding Custom Commands

There is one more step you can take when making an app scriptable: adding custom commands. In earlier scripts, you toggled the completed flag of a task directly. But wouldn’t it be better – and safer – if scripts didn’t change the property directly, but instead used a command to do this?

Consider the following script:

mark the first task as "done"
mark task "Feed the cat" as "not done"

I’m sure you’re already reaching for the SDEF file and you would be correct: the command has to be defined there first.

There are two steps that need to happen here:

  1. Tell the application that this command exists and what its parameters will be.
  2. Tell the Task class that it responds to the command and what method to call to implement it.

Inside the Scriptable Tasks suite, but outside any class, add the following at the Insert command here comment:

<command name="mark" code="TaSktext">
  <direct-parameter description="One task" type="task"/>
  <parameter name="as" code="DFLG" description="'done' or 'not done'" type="text">
    <cocoa key="doneFlag"/>
  </parameter>
</command>

“Wait a minute!” you say. “Earlier you said that codes had to be four characters, and now I have one with eight? What’s going on here?”

When defining a method, you provide a two part code. This one combines the codes or types of the parameters – in this case a Task object with some text.

Inside the Task class definition, at the Insert responds-to command here comment, add the following code:

<responds-to command="mark">
  <cocoa method="markAsDone:"/>
</responds-to>

Now head back to Task.swift and add the following method:

func markAsDone(_ command: NSScriptCommand) {
  if let task = command.evaluatedReceivers as? Task,
    let doneFlag = command.evaluatedArguments?["doneFlag"] as? String {
    if self == task {
      if doneFlag == "done" {
        completed = true
      } else if doneFlag == "not done" {
        completed = false
      }
      // if doneFlag doesn't match either string, leave un-changed
    }
  }
}

The parameter to markAsDone(_:) is an NSScriptCommand which has two properties of interest: evaluatedReceivers and evaluatedArguments. From them, you try to get the task and the string parameter and use them to adjust the task accordingly.

Quit and build and run your app again. Check the dictionary in the Script Editor, and delete and re-import it if the mark command is not showing:

Making a mac app scriptable tutorial: Scriptable Tasks Dictionary 3

You should now be able to run the 7. Custom Command.scpt scripts and see your new command in operation.

Note: Swift 3 changed the way the commands are sent to the objects. AppleScript still works as expected, but the mark command does not work in JavaScript. I have added manual toggling of the completed property to the JavaScript version of 7. Custom Command.scpt but left the original there too. Hopefully it will work after an update.

Where to Go From Here?

You can download the final version of the sample project here.

There wasn’t room to cover inter-app communication in this making a mac app scriptable tutorial, but to see how to work between apps, check out 8. Inter-App Communication.scpt for some examples. This script gathers a list of incomplete tasks due today and tomorrow, inserts them into a new TextEdit file, styles the text and saves the file.

For more information about scriptable apps, the official Apple docs on Scriptable Applications are a good start, as is Apple’s Overview of Cocoa Support for Scriptable Applications.

Interested in learning more about JXA? Check out the Introduction to JavaScript for Automation Release Notes.

I hope you enjoyed this making a mac app scriptable tutorial; if you have any questions or comments, please join the forum discussion below!