A with block is used to specify certain external attributes of Apple events sent to target applications from inside the block. Two types of with block are defined: a timeout block and a transaction block.
Recall from "Apple Events" in Chapter 3 that during interapplication communications, the sender of an Apple event may attach to that Apple event a specification of how long it is willing to wait for a reply. This is the Apple event's timeout period. If the target does not reply within the specified timeout period, for whatever reason (the requested operation might be too lengthy, the target application might be otherwise engaged, and so forth), the system stops waiting for a reply and reports to the sender that the Apple event timed out. This report arrives as an error; your script can handle this error and proceed (see "Errors," later in this chapter).
This entire mechanism is valuable, because (among other things) it rescues the sender from hanging indefinitely while waiting for the target to reply; if the target takes too long, the sender is able to proceed nonetheless. Of course, the sender must then do without any reply from the target, but a script can take account of this possibility. For example, reporting the problem to the user and proceeding, or even terminating in good order, is surely preferable to hanging or appearing to hang while waiting for a reply that is taking a long time to arrive and that may, indeed, never come.
All Apple events sent to target applications have a default timeout value of one minute. This is a good compromise between waiting sufficiently long for lengthy operations to complete and waiting so long (or not having any timeout at all) that a script can hang or appear to hang. If this value is acceptable to you, you don't need a timeout block to change it.
To change the timeout value temporarily using a timeout block , use this syntax:
with timeout of integer second[s] -- code affected by timeout value end timeout
This affects only code within the block; afterwards, Apple events revert to the default timeout value. To wait indefinitely, use an extremely large integer. The maximum permissible value is 8947848. In Tiger there is no penalty for using a larger number, but it will be treated as 8947848; on earlier systems, a larger number will cause a runtime error ("The result of a numeric operation was too large ").
To illustrate, we'll command the Finder to perform an operation so long that without a timeout specification, it probably wouldn't have time to replywe'll ask it to cycle down the entire hierarchy looking for a certain kind of file:
with timeout of 100000 seconds tell application "Finder" get every application file of entire contents
If we don't provide a timeout block, this code will time out before the Finder is finished, and we'll get an error: "Finder got an error: AppleEvent timed out ." (Even if the Apple event times out, the Finder will still be cycling down the entire hierarchy, and it will keep doing so until it finishes. So don't run that example unless you're not planning on using the Finder for a while!)
A problem that can arise with interapplication communications is that a target application is promiscuous. While you're being a sender and talking to a target application, some other sender can come along and talk to it as well. If this happens in the middle of a series of Apple events from you, it can alter the state of the target application, messing up what you're trying to accomplish.
The Apple event solution to this is the transaction . A transaction is a kind of permission slip allowing you to unify multiple commands. You start by asking for this permission slip, and the target application returns a transaction ID of some sort. You then continue sending the target application Apple events, showing it the transaction ID every time. When you're done, you tell the target that you're done (showing it the transaction ID, of course), and that transaction comes to an end. Not every scriptable application implements transactions (would that they did); a commonly used application that does is FileMaker Pro.
The target application itself is responsible for deciding how to implement the notion of a transaction. All you care about is that state should be conserved throughout the multiple commands of a single transaction. FileMaker's approach is to implement a transaction as monopolization; once you've asked for the permission slip and obtained the transaction ID, FileMaker will simply refuse to respond to any Apple event that does not show the transaction ID until you tell it the transaction is over, at which point it returns to its normal state of promiscuity.
The way to obtain, show, and release the transaction ID is by wrapping your transactional communications in a transaction block , which looks like this:
with transaction -- transactional code end transaction
All the actual business of dealing with the transaction ID is handled transparently for you. The with transaction line causes an Apple event to be sent to the current target asking for the transaction ID. Then all the application-targeted Apple events inside the block are accompanied by this transaction ID. Finally, the end transaction line sends the current target an Apple event (accompanied by the transaction ID) telling it to leave transaction mode.
In this example, we monopolize FileMaker Pro long enough to create a Find request and perform it:
tell application "FileMaker Pro" with transaction tell database 1 show every record set f to create new request set cell "lastname" of f to "neuburg" find end tell end transaction end tell
There is one important thing to notice about that code: the transaction block is inside the tell block. It is essential to structure your code this way; the application with which you want to carry on a transaction must be the target when the with transaction line is encountered, so that AppleScript knows where to send that first Apple event asking for the transaction ID. Unfortunately, this means we run smack into a bizarre terminology problem. It turns out that FileMaker Pro's dictionary also implements the opening and closing transactional Apple events as the commands begin transaction and end transaction. This means that when you say end transaction inside a tell block addressed to FileMaker Pro, it is seen as FileMaker's end transaction command, not as the closing of the transaction block. The script then won't compile. The workaround, which is terribly annoying, is to delete the word transaction from the end transaction line whenever you are about to compile.
You might worry about what happens if something goes wrong in the middle of the transaction block. What if we say something that generates an error? We'll never reach the end of the transaction block, and that means we'll leave FileMaker Pro in a transaction state, refusing to respond to Apple events. You're perfectly right to worry about this; you certainly don't want to leave FileMaker Pro in transaction mode. If FileMaker Pro were to get into such a state, you couldn't even quit it, because the Quit menu item is implemented with an Apple eventand FileMaker Pro won't listen to that Apple event, because it doesn't supply the transaction ID! It turns out, though, that AppleScript solves this problem transparently. If an error is encountered during a transaction block, AppleScript sends the target the Apple event that ends the transaction. I suspect that the transaction block is wrapped in a sort of invisible try block. In any case, it's really all very nicely implemented.