DekGenius.com
[ Team LiB ] Previous Section Next Section

24.1 Applets

An applet is a compiled script wrapped up in a simple standalone application shell (see Section 4.6). To make a script into an applet, save it from the Script Editor as an application instead of as a compiled script. You elect this choice in the Save As dialog. The result is a standalone application. If you open the application from the Finder (by double-clicking it, for example), the script runs.

Alternatively, you can now save a script as an application bundle. From the outside, the result looks and works like an applet, exactly as described in this chapter. Since it's a bundle, though, you can do things with it that you can't do with an old-style applet, such as storing extra resources inside it; for an example, see Section 24.1.4, later in this chapter. Also, an application bundle can call scripting additions contained within itself; see Section 20.4. Keep in mind that this format is not backward-compatible with earlier system versions.

A script still remains editable from inside the applet. The only trick is that now you must open the script for editing in a different way. You can't edit it by double-clicking it from the Finder, since that runs the applet. But the Script Editor can still open it. You can even have an applet open for editing in the Script Editor, save it without closing it, and then double-click it in the Finder to run it, as a way of testing while developing. (But you can't save an applet's script into the applet while the applet is actually running, for obvious reasons.) And there's another way to open an applet for editing, which we'll come to in a moment.

24.1.1 Applet Options

When you select Application (or Application Bundle) in the Save As dialog, some further options come into play. These options affect the way the application will behave.

One option is Show Startup Screen. If this is checked, the script's description is used to form a dialog which is displayed when the applet is started up, as a kind of introductory splash screen. In Script Editor, the description may be typed into the Description tab at the bottom of the window. It is styled text, and the styling is maintained in the startup screen dialog. The dialog offers the choice to run the applet's script or to quit without running it.

Another option is Stay Open. The default behavior of an applet is to run its script and then quit automatically. But if Stay Open is checked, it doesn't quit automatically after running. A Stay Open applet has a Quit menu item, so the user can quit it manually. (Actually, even a non-Stay Open applet has a Quit menu item, but one is unlikely to notice this unless its run handler takes a long time. This can confuse the user; see the next section.) It also has a File menu item that lets the user suppress the startup screen (if the applet was originally saved with Show Startup Screen checked), and an Edit menu item that lets the user open the applet's script in the Script Editor.[1]

[1] But the Edit menu item is not working for application bundles as of this writing.

An applet that was saved with the Show Startup Screen option unchecked, or whose startup screen has been suppressed by the user, can be forced to show its startup screen dialog by holding down the Control key as the applet starts up.

While the startup screen dialog is showing, the applet's menus are active. Thus, the user can always edit an applet's script, even if the applet is not set to Stay Open, by starting up the applet and holding down the Control key to bring up its startup screen dialog, then choosing the Edit menu item.

To prevent the applet from being editable by the user, you can save it as Run Only. Keep in mind that this means even you can no longer edit the applet; if you have no other copy of the script, you lose all ability to edit the applet's script forever.

24.1.2 Applet Handlers

Certain handlers in an applet script, if present, will be called automatically at certain moments in the lifetime of the applet. Because of their special status, none of these handlers takes parentheses in the first line of its definition; they are not called as handlers, but are responding to predefined commands (events):


run

The run handler, whether implicit or explicit (Section 8.5.3), is called when the applet is started up, either by the user opening it from the Finder or by a script targeting it. To start up an applet without calling its run handler, tell it to launch.


reopen

A reopen handler, if present, is called when the already running applet is summoned to the front by such means as being double-clicked in the Finder or having its icon clicked in the Dock. Merely switching among applications with figs/command.gif -Tab, or telling the applet to activate, does not send a reopen event.


idle

An idle handler, if present, is called as soon as the run handler finishes executing, and then again periodically while a Stay Open applet is running. The value returned by the idle handler is a real representing the number of seconds before the idle handler should be called again. A return value of 0, or any other value that can't be coerced to a positive real, is treated as 30.


quit

A quit handler, if present, is called when the applet is about to quit. If it is a Stay Open applet, this might be because the user has chosen its Quit menu item; if not, it might be because the applet has been started up and its run handler has finished executing. If the quit handler wishes to permit the applet to quit, it must give the continue quit command.

An applet having a quit handler that does not give the continue quit command will appear to the user to be impossible to quit (except by force-quitting).


So, for example, here's an annoying little Stay Open applet:

on run
        display dialog "Howdy!"
end run
on quit
        display dialog "Farewell!"
        continue quit
end quit
on idle
        beep
        display dialog "Get to Work!"
        return 1 * minutes
end idle

An applet is scriptable with respect to its handlers; that is, you can tell an applet to run, reopen, idle, or quit, and it will execute the respective handler if it has one. If you tell an applet to run and it has no run handler code, it simply starts up if it isn't running already. If you tell an applet to run and it does have a run handler, and it wasn't running already, the run handler will be called twice—once because you targeted it, and again because you told it to run. To prevent this, first tell the applet to launch, and then tell it to run. (To prevent the run handler from being called at all, tell the applet to launch and don't tell it to run.) If you tell an applet to quit and it has no quit handler, it simply quits. If you tell an applet to idle and it has no idle handler, or to reopen and it has no reopen handler, nothing happens (but the calling script may receive an error).

You are also free to add handlers of your own to an applet's script. If you do, then the applet becomes scriptable with respect to these handlers. You call them like ordinary handlers. For example, suppose we have an applet howdy whose script goes like this:

on sayHowdy(toWhom)
        activate
        display dialog "Howdy, " & toWhom
end sayHowdy

Then we can say in another script:

tell application "howdy"
        sayHowdy("Matt")
end tell

The value is returned as one would expect; here, the calling script receives the value {button returned:"OK"} if user presses the OK button.

An applet has no dictionary. This means that when you call an applet's handler from another script, AppleScript has no way of knowing whether the applet contains such a handler, or, if it does, what its parameters should be. But it doesn't need to know. Without attempting to resolve it as terminology, AppleScript converts your call into the Call·subroutine command ('ascr/psbr') (see Appendix A). Essentially, it just recodes your call as a record and throws that record at the applet. It is up to the applet to decide how to respond; and it decides this correctly, all by itself. If you call an applet handler that doesn't exist, or if you call an applet handler with parameters that are not in accordance with those of the handler definition, the applet returns an error at runtime. The need to be able to code a handler call as an Apple event that can be blindly sent from one process to another in this way helps to explain the peculiar rules of AppleScript's handler-calling syntax (Section 8.3).

The idle handler should not be treated as ensuring a precise measure of time. The time interval returned is merely a request not to be called until after the interval has elapsed. I am not entirely clear on what the time interval is measured from; experiments returning 0 seemed to suggest that it was measured from when the idle handler was last called, not from when it last returned, but this didn't seem to be true for other values. If your goal is to run a script at precise times or intervals, you might be happier using a utility to handle this for you. For example, see http://www.sophisticated.com/products/ido/ido_ss.html for iDo Script Scheduler, a commercial product that runs scripts at specified times.

The question arises of how to interrupt a time-consuming applet. Suppose the run handler takes a long time and the user wishes to stop it and quit. Even a non-Stay Open applet has a Quit menu item, both in the menu bar and in the Dock, so the user might try choosing one; but this won't work. The user can cause an error by pressing figs/command.gif -Period, which your script can catch and respond by quitting; but the user might not think of this. The user can force-quit, but then any cleanup operations in your quit handler won't be performed. The best you can do is probably something like this:[2]

[2] This example and the entire discussion of the problem come from Paul Berkowitz.

global shouldQuit
global didCleanup
on run
        set shouldQuit to false
        set didCleanup to false
        try
                -- lengthy operation goes here
                repeat with x from 1 to 10
                        if shouldQuit then error
                        say (x as string)
                        delay 5
                end repeat
        on error
                tell me to quit
        end try
end run
on quit
        if not didCleanup then
                -- cleanup operation goes here
                say "cleaning up"
                set didCleanup to true
        end if
        set shouldQuit to true
        continue quit
end quit

While the run handler of that example is executing, here's what the user can do, and what our code will do in response:

  • The user presses figs/command.gif -Period. We catch the error, call our own quit handler, clean up, and quit in good order.

  • The user chooses Quit from the applet's Application menu. This calls our quit handler, but when we say continue quit we don't succeed in quitting—we merely resume the run handler. Therefore we also set a global indicating that the user is trying to quit. The resumed run handler notices this, deliberately errors out as if the user had pressed figs/command.gif -Period, and we catch the error and call our own quit handler, and quit in good order. We would perform our cleanup operations twice in this case, but that is prevented by another global.

  • The user chooses Quit from the applet's Dock menu. This has no effect upon our applet. I regard this as a bug.

  • The user force-quits our applet. This stops the applet dead, but of course there is no cleanup.

24.1.3 Droplets

A droplet is simply an applet with an open handler:


open

An open handler, if present, will be called when items are dropped in the Finder onto the droplet's icon. It should take one parameter; this will be a list of aliases to the items dropped.

The open handler's parameter is a command parameter, not a handler parameter, so it does not have to be expressed in parentheses in the first line of the definition.

If a droplet is started up by double-clicking it from the Finder, then its run handler is executed and its open handler is not. But if it is started up by dropping items on it in the Finder, then it's the other way around: its open handler is executed and its run handler is not. Once a droplet is running (assuming it is a Stay Open droplet), the open handler can be executed by dropping items onto the droplet's icon in the Finder. The open handler is also scriptable, using the open command, whose parameter should be a list of aliases.

In this simple example, the droplet reports how many folders were dropped on its icon:[3]

[3] This technique would have to modified in order to work on machines where the Finder's response to get kind of gives answers in a language other than English.

on open what
        set total to 0
        tell application "Finder"
                repeat with f in what
                        if kind of f is "folder" then set total to total + 1
                end repeat
        end tell
        display dialog (total as string) & " folder(s)"
end open

24.1.4 Persistence

Persistence of top-level entities (see Section 7.6 and Section 9.2.2) works in an applet. The script is re-saved when the applet quits, maintaining the state of its top-level environment.

So, for example, the following modification to the previous example would cause an applet to report the count of folders that had ever been dropped on it, not just the count of folders dropped on it at this moment:

property total : 0
on open what
        tell application "Finder"
                repeat with f in what
                        if kind of f is "folder" then set total to total + 1
                end repeat
        end tell
        display dialog (total as string) & " folder(s)"
end open

On the other hand, this persistence ends as soon as the applet's script is edited. If you're still developing an applet, or likely to edit it further for any reason, you might like a way to store data persistently with no chance of losing it. The new application bundle format supplies a solution. An application bundle appears and behaves in the Finder just like an applet, but is in reality a folder. When it runs, path to me is the bundle's pathname. This means we can perform persistent data storage in a separate script file inside the bundle; the user won't see this separate file, and as long as we don't deliberately open it in the Script Editor, its data will persist even when we edit the applet's main script.

To illustrate, let's return to the example in Section 9.6.2. This code is just the same as in that example, except that we now assume we are an application bundle, and the first line has been changed to store the data inside the bundle:

set thePath to (path to me as string) & "myPrefs"
script myPrefs
        property favoriteColor : ""
end script
try
        set myPrefs to load script file thePath
on error
        set favoriteColor of myPrefs to text returned of ¬
                (display dialog "Favorite Color:" default answer ¬
                        "" buttons {"OK"} default button "OK")
        store script myPrefs in file thePath replacing yes
end try
display dialog "Your favorite color is " & favoriteColor of myPrefs

Now we save the script as an application bundle, and we have a single file, an applet, which behaves correctly: the first time it runs, it asks for the user's favorite color; the next time it runs, it remembers the user's favorite color and presents it. And it doesn't forget the user's favorite color if we now edit this script.

    [ Team LiB ] Previous Section Next Section