Implementing an AppleScript Action

The Xcode template for an AppleScript Action project includes an uncompiled script file named main.applescript. An AppleScript-based action must, at the minimum, add to main.applescript the scripting code that accesses the services of one or more applications and completes the stated purpose of the action. An AppleScript-based action can also rely on other components for its implementation, such as:

Regardless of the possible implementation components, an AppleScript-based action always has a main.applescript file and an invisible instance of AMAppleScriptAction. This instance owns the action bundle’s nib file, has access to main.applescript, and provides a default implementation of the method used to run the action behind the scenes, runWithInput:fromAction:error:. (AMBundleAction, the superclass of AMAppleScriptAction, declares this method.)

The Structure of the on run Command Handler

The template main.applescript contains only the following skeletal structure of the on run command handler:

on run {input, parameters}
    -- your code goes here
    return input
end run

This on run command handler has two positional parameters, input, and parameters, The input parameter contains (in most instances) the output of the previous action in the workflow; it is almost always in the form of a list. The parameters parameter contains the settings made in the action’s user interface; the values in parameters (a record object) are typically set via the Cocoa bindings mechanism but can also be manually updated. The template code finally returns input as its output; your action should always return something as output, even if it what is given it as input.

You can use any valid AppleScript command, object, reference, expression, and statement in main.applescript. However, the following steps are suggested for an action’s run handler (not all steps are applicable to every kind of action).

Some actions specify in their AMAccepts property that their input is optional (that is, the Optional subproperty is set to <true/>). This setting allows users to select a “Ignore Result From Previous Action” pop-up item in the action’s view. If your action has made input optional in its AMAccepts property, you need to determine if the user has made this choice by testing the ignoresInput field of parameters; if it is set to true. then you do not need to do anything with the input object.

You can include subroutines in main.applescript . For example, you might want to write a subroutine for returning localized strings (described in “Localized Strings”).

You can execute command-line tools or shell scripts for your script. The following script fragment runs the screencapture tool:

set screencapture to "/usr/sbin/screencapture "
-- here set up arguments of screencapture
do shell script screencapture

A caveat about using the do shell script command is that, because of the threading architecture, it makes the user interface unresponsive until the shell script completes or the user presses Command-. (period).

An action’s scripts can also invoke Objective-C methods that you have implemented in a custom class; for example, the following statement calls a class method named isDVDPlayerRunning:

set dvdRunning to call method "isDVDPlayerRunning" of class "TakeScreenshot"

“Hybrid Actions” discusses actions that contain a mix of AppleScript and Objective-C code.

Other AppleScript Scripts

An AppleScript action can contain scripts other than main.applescript. These additional scripts typically contain two types of handlers:

AppleScript Studio allows you to request handlers for the update parameters, parameters updated, activated, and opened commands in the Automator Action View group in the AppleScript pane of the Interface Builder inspector.

As an example of AppleScript Studio event handlers, consider a pop-up menu for various date formats. When the user chooses a item from this pop-up menu, the action changes an example showing the effect of this format change. Listing 1 shows an event handler that is invoked when the user chooses a pop-up item for saving a screen capture to a “Specific File”.

Listing 1  An event handler for displaying a Save panel

on clicked theObject
    if name of theObject is "choose output" then
        -- Setup the properties in the 'save panel'
        tell save panel
            set title to "Choose an output file to save the screenshot to"
            set prompt to "Save"
            set required file type to "pdf"
            set treat packages as directories to false
        end tell
        if (display save panel in directory "" with file name "") is equal to 1 then
            set contents of text field "output file" of super view of theObject to path
                name of save panel
            set contents of popup button "output choice" of super view of theObject to 1
            set enabled of button "choose output" of super view of theObject to true
            set enabled of text field "output file" of super view of theObject to true
        end if
    end if
end clicked

Hybrid Actions

An action implementation can contain a mix of AppleScript and Objective-C code. Usually such hybrid actions are AppleScript-based—in other words, they have a main.applescript file containing the run command handler. However, it is possible to have Objective-C actions that use the Cocoa scripting classes to load (or create) and execute scripts. See “Implementing an Objective-C Action” for a discussion of the latter type of hybrid action.

Why would you want to create a hybrid action? One reason is to have greater control of the user interface. An Objective-C class that links with the Application Kit has access to the full range of programmatic resources for managing the appearance and behavior of user-interface objects.

But perhaps the most common reason for hybrid actions is to get access to system resources that a script could not get on its own. Scripts can call Objective-C instance methods with the call method command, but only if the target object is something that the script can address (for example, call method "title" of window 1). Sometimes, an AppleScript Studio script does not have access to a valid object for a call method command. To get around this limitation, you can create a simple custom class that implements one or more class methods. The first argument of these methods is the target (receiver) of an instance method that the class method wraps. Listing 2 illustrates this approach.

Listing 2  Implementing a custom Objective-C method to be called by a script

#import "CreatePackageAction.h"
@implementation CreatePackageAction
+ (BOOL)writeDictionary:(NSDictionary *)dictionary withName:(NSString *)name
    return [dictionary writeToFile:name atomically:YES];

In a script you can then use a call method "m" of class "c" with parameters{ y,z} statement to call this method (Listing 3).

Listing 3  A script calling the Objective-C method

set descriptionRecord to
set rootName to call method "lastPathComponent" of rootFilePath
set descriptionFilePath to temporaryItemsPath & rootName & "description.plist"
call method "writeDictionary:withName:" of class "CreatePackageAction" with parameters
     {descriptionRecord, descriptionFilePath}

Updating Non-bound Parameters

A property of all actions, including AppleScript-based ones, is a record (equivalent in Objective-C to a dictionary) containing values reflecting the settings users have made in the action’s user interface. When Automator runs an AppleScript action by calling its run command handler, it passes in these values in the parameters parameter. Most actions use the Cocoa bindings mechanism to set the parameters property. However, you may choose to forgo bindings in favor of more direct approach.

The Automator application defines several commands for AppleScript Studio. Among these are update parameters and parameters updated, which can be attached to an action’s view. The update parameters command is sent when the an action’s parameters record need to be refreshed from the values on the user interface. The parameters updated command is sent when the parameters record changes any of its values.

To specify handlers for these commands in Interface Builder, follow the usual AppleScript Studio procedure:

  1. Select the action’s view.

  2. In the AppleScript pane of the Info window, expose the commands under Automator Action View (see Figure 1).

  3. Click the check boxes for update parameters and parameters updated.

  4. Specify a script file for the command handlers.

Figure 1  Selecting the parameters-related handlers for action views
Selecting the parameters-related handlers for action views

Automator calls the handler for update parameters just before it runs the action. In this handler, the action should get the settings from the user interface and update the parameters record with them. Listing 4 provides an example of an update parameters handler.

Listing 4  An “update parameters” command handler

on update parameters theObject parameters theParameters
    set |destinationPath| of theParameters to (the content of text field "folder path" of the destination_folder_box) as string
    set |textInput| of theParameters to (the content of text field "text input" of the text_source_box) as string
    set |fileName| of theParameters to (the content of text field "file name" of the file_name_box) as string
    set |chosenVoice| of theParameters to the title of popup button "voice popup" of voice_box
    set |textSource| of theParameters to ((the current row of matrix "source matrix" of the text_source_box) as integer) - 1
    return theParameters
end update parameters

Always return the updated parameters object as the final step in this handler (theParameters in the above example).