Tell By Variable (herein of the Double-Tell)

Tell By Variable

Preface
The original version of this tip was written several years ago to address a specific problem that had frustrated scripters in the classic Mac OS for ages: When you installed a precompiled script on another computer, it often presented a dialog asking the user to locate targeted applications the first time it was run. This usually happened because the targeted applications had been given different names on the run time computer, but it sometimes happened even when the target application names were the same. This was commonly seen as raising an obstacle to the seamless use of AppleScripts which are intended for general distribution, for example, as software installers or utilities. A popular workaround in the old days was to use a variant of the so-called “double-tell” technique described here. (Another popular technique, still in use today, was to incorporate raw event codes in a script, but we won’t go into any detail here about that somewhat advanced technique.)

Nowadays, in Mac OS 9 and Mac OS X, the issue has largely gone away. For a detailed explanation of today’s preferred technique, read the Launch by Creator or Identifier tip. If you’re interested in some history, read on.

Partial solutions to the problem discussed here were incorporated into several releases of AppleScript after Mac OS 8.0, making it less and less necessary to resort to the double-tell technique. For example, in September 1998, Andy Bachorski, then at Apple, said this:

In July 1999, Chris Espinosa of Apple said something similar:

In AppleScript 1.4, released with Mac OS 9 in October 1999, A new using terms from statement allows tell blocks to use the terminology dictionary of a specific application at compile time. The Mac OS 9 Technote specifically stated that this new statement was designed to replace the double-tell technique, which had been officially recommended in the Mac OS 8.6 Technote. Scripts using the new statement will not work under earlier versions of AppleScript or the Mac OS. Other changes were also made in AppleScript 1.4 to further reduce the incidence of requests to a script’s user to locate a targeted application. For example, the system automatically located scriptable system components in the Scripting Additions folder, the Control Panels folder and the Extensions folder with the given name.

The “Double-Tell”

Many scripters have sought to generalize tell statements, such as tell application “TextEdit”, by assigning the target application’s name to a variable and telling the variable to do something. This works, but only with commands that are known to AppleScript generally, such as built-in AppleScript commands and global scripting addition commands; that is, commands that are not unique to the target application. This tip explains how to make the technique work with all commands, including commands implemented by applications, in versions of the Mac OS before Mac OS 9. This is the famous “double tell” technique.

Here is the basic model, in which the script tells the Finder to open the target application file by its creator, assigns the run-time name of the application to a variable, and uses a tell application statement with the variable to tell the target application to perform more than one action:


tell application "Finder"
   open application file id "ttxt"
   set appName to name of result --"TextEdit"
end tell

tell application appName
   activate
   quit
end tell

(That script would require a minor modification to work on Mac OS X, where the Finder’s open command does not return a result.)

Alternatively, under System 7, you could assign the running application’s process to a variable, and tell the variable to do something. Note the absence of the word “application” in the tell statement; it is not needed because the process is application “Scriptable Text Editor”. In either case, the variable can then be used anywhere in your script.


tell application "Finder"
   open application file id "quil"
   set appName to name of result --"Scriptable Text Editor"
   set appProcess to application process appName --application "Scriptable Text Editor"
end tell

tell appProcess --don't need the keyword "application"
   activate
   quit
end tell

The foregoing script would not work under Mac OS 8, because application process appName returned a true application process rather than an application serial number as it did under System 7, and a Finder process did not understand application commands. The workaround was to append an as «class psn » coercion to the end of the set appProcess statement to force it to behave under Mac OS 8 as it did under System 7.

Unfortunately, these models worked only with commands that were part of the basic AppleScript language and scripting additions known to the compiler. You cannot compile scripts that use unique commands defined in the target application’s dictionary in this fashion, because the compiler does not know where the application and its dictionary are located at compile time if you do not provide its name until run time. For example, the following script would not compile because the cut command in the old Scriptable Text Editor only knew how to take a word reference as a parameter within the Scriptable Text Editor’s dictionary:


tell application "Finder"
   open application file id "quil"
   set appName to name of result
end tell

tell application appName
   activate
   set text of document 1 to "This is a test"
   cut word 2 of document 1 --error
   quit saving no
end tell

You could only compile a script to cut words if you explicitly identified “Scriptable Text Editor” as a string constant in the tell statement, using the name of the application as it appears on the compiling machine. However, it may require the user to locate the target application the first time the script is run on another machine, which is the evil we are trying to avoid. The second block of the previous example must appear as follows in order to compile:


tell application "Scriptable Text Editor"
   activate
   set text of document 1 to "This is a test"
   cut word 2 of document 1 --no error
   quit saving no
end tell

Here is the comprehensive solution, which successfully combines both methods to solve all of the problems. By the magic of AppleScript, you can have it both ways. Simply use the “double-tell” technique: two tell application statements, one using a string constant and the other a variable, with one nested inside the other:


tell application "Finder"
   open application file id "quil"
   set appName to name of result
end tell

tell application "Scriptable Text Editor"
   tell application appName
      activate
      set text of document 1 to "This is a test"
      cut word 2 of document 1
      quit saving no
   end tell
end tell

Telling the application”using the actual name of the application as a string literal as it appears on the compiling machine”satisfies the compiler, letting it know where the target application’s dictionary is located. Telling the application again”using a variable that is set at run time to the application’s name as it appears on the user’s machine”directs the commands to the target application using the name by which it is known on the run-time machine. The script can therefore be compiled, and it can run on any machine no matter what the application’s name may be on that machine, without requiring the user to locate it at run time. Further, it apparently does not matter for these purposes which tell statement is the inner or outer one. The only special requirement is that the target application be running by the time the tell block is reached.

This solution is made possible by the fact that AppleScript allows multiple tell application statements to be nested one within another. The innermost block of AppleScript statements will recognize commands referenced by all of the enclosing tell statements. You can therefore refer to the target application with two tell statements, one nested within the other”one referring to the target application by the string constant known to the compile-time machine, and the other referring to the target application by a variable that has been set to the application’s name on the run-time machine. The target application and its dictionary will be found by one tell statement or the other, depending on its name.

The “double-tell” technique described here can be used in slightly different contexts, as well. For example, under System 7 one could obtain a list of running processes from the scriptable Finder, detect a particular desired process by its creator, then script it, on the following model:


tell application "Finder"
   set appNames to name of every application process whose creator type is "quil"
end tell

if appProcesses is not {} then
   tell application "Scriptable Text Editor"
      tell application (item 1 of appNames)
         --statements
      end tell
   end tell
end if

Under Mac OS 8, remember to append as «class psn ».

Although some script authors prefer to use the “process” technique, I find the “name” technique more congenial”especially under Mac OS 8 where the “process” technique requires use of the ugly as «class psn » workaround. Although some have claimed that the “process” technique is more reliable, I have not seen this in my own work. Both techniques inexplicably fail with some applications when a script is run on another computer where the application’s name is different. I have not yet tested this hypothesis, but I speculate that the problem occurs only with applications which implement “dynamic” Apple event dictionaries, dictionaries whose content is generated at run time.

Until Mac OS 9 effectively cured the problem with its using terms from statement, all scripts meant for distribution in compiled form were well advised to use the launch-by-creator and double-tell techniques described here to control target applications. But it was essential to test them to ensure that they worked with a specific target application. Here is an example, incorporating all of the techniques decribed here:


try
	
   tell application "Finder"
      set appPath to application file id "quil" as string
      set appName to name of application file id "quil"
   end tell
	
   launch application appPath --does not open untitled window
	
   tell application "Scriptable Text Editor"
      tell application appName
         activate
         set newDoc to make new document --open untitled window ourselves
         set text of newDoc to "This is a test"
         cut word 2 of newDoc
         quit saving no
      end tell
   end tell
	
on error number -1728
   error "Could not find Scriptable Text Editor."
end try

I have no idea who first discovered or invented the double-tell solution.

Remember that most of what is described here is no longer relevant in Mac OS X. See the Launch By Creator or Identifier tip for the modern way to deal with these issues.