Getting Started with Handlers (Part 2 of 3)

Last month’s column focused on providing an introduction to handlers, discussed the benefits of using subroutine handlers, explored writing and calling subroutine handlers, and discussed handler parameter usage. You can read last month’s column here.

In this month’s column, we will continue discussing handlers, in preparation for using AppleScript Studio to create interface-rich AppleScript solutions that have the same look and feel of any other Mac OS X application.

Variable Scope and Handlers

When writing handlers, by default, a variable that is defined within a handler is local only to the handler itself. In other words, that variable may not be accessed by other levels of the script containing the handler.

To better illustrate this concept, let’s take a look at an example. The following handler may be used to check for the existence of a folder in a specified location, create it if it does not already exist, and then open that folder. This handler accepts two parameters, an output folder path and a name for the new folder. Within the handler, a variable named theFolder is defined and assigned a reference to the folder.


on checkFolder(theOutputFolder, theFolderName)
	tell application "Finder"
		-- Determine whether the specified folder exists
		if folder theFolderName of theOutputFolder exists then
			
			-- Define a variable that references the existing folder
			set theFolder to folder theFolderName of theOutputFolder
			
			-- Create a new folder, if it doesn't exist
		else
			set theFolder to make new folder at theOutputFolder with properties {name:theFolderName}
		end if
		
		-- Open the folder
		open theFolder
	end tell
end checkFolder

This handler may now be called at any time throughout the script by referencing the handler’s name, and providing values for its parameters. For example, the following call would cause the handler to execute and create a folder named “My Folder” on the desktop.


checkFolder(path to desktop folder, "My Folder")

Within the scope of this handler, the variable theFolder is considered to be local, and cannot be referenced by any other level of the script. For example, if I try to run the following code, an error will occur at run time indicating that the variable theFolder is not defined.


-- Check for the folder
checkFolder(path to desktop folder, "My Folder")

-- Reposition the folder's window
tell application "Finder"
	set position of window of theFolder to {10, 50} -- Error occurs here
end tell

on checkFolder(theOutputFolder, theFolderName)
	tell application "Finder"
		-- Determine whether the specified folder exists
		if folder theFolderName of theOutputFolder exists then
			
			-- Define a variable that references the existing folder
			set theFolder to folder theFolderName of theOutputFolder
			
			-- Create a new folder, if it doesn't exist
		else
			set theFolder to make new folder at theOutputFolder with properties {name:theFolderName}
		end if
		
		-- Open the folder
		open theFolder
	end tell
end checkFolder

In order to allow other levels of the script access to the variable theFolder, I can declare the variable to be global in nature. This can be done in multiple ways.

Declaring a Global at the Top Level of the Script

To make a variable accessible to all levels of your script, you must declare it as global at the top level of the script. Please note that this must be done prior to any code that references the variable. Many AppleScript developers tend to declare global variables at the top of their script, to ensure that this is always the case. For example:


-- Declare the variable as a global
global theFolder

-- Check for the folder
checkFolder(path to desktop folder, "My Folder")

-- Reposition the folder's window
repositionFolderWindow()

-- Close the folder
tell application "Finder"
	close theFolder
end tell

on checkFolder(theOutputFolder, theFolderName)
	tell application "Finder"
		-- Determine whether the specified folder exists
		if folder theFolderName of theOutputFolder exists then
			
			-- Define a variable that references the existing folder
			set theFolder to folder theFolderName of theOutputFolder
			
			-- Create a new folder, if it doesn't exist
		else
			set theFolder to make new folder at theOutputFolder with properties {name:theFolderName}
		end if
		
		-- Open the folder
		open theFolder
	end tell
end checkFolder

on repositionFolderWindow()
	tell application "Finder"
		-- Reposition the folder's window
		set position of window of theFolder to {10, 50}
	end tell
end repositionFolderWindow

In the example code above, the variable theFolder is still assigned its initial value within the checkFolder handler. The variable is then referenced at the top level of the script, after the checkFolder handler has been called, as well as within a new handler, called repositionFolderWindow. At the top level of the script, the variable theFolder has also been declared global in nature. Because of this, all levels of the script can access the variable’s value, once it has been defined within the checkFolder handler, and this code will run successfully.

An alternative to declaring a variable as global at the top level of the script is to make it into a property. Properties must be declared at the top level of the script, and are defined when the script is compiled, rather than when the script is run. Like globals that are declared at the top level of a script, properties are available to all levels of a script. The following example code demonstrates how a property may be used to achieve the same result as using a global variable:


-- Define the property
property theFolder : ""

-- Check for the folder
checkFolder(path to desktop folder, "My Folder")

-- Reposition the folder's window
repositionFolderWindow()

-- Close the folder
tell application "Finder"
	close theFolder
end tell

on checkFolder(theOutputFolder, theFolderName)
	tell application "Finder"
		-- Determine whether the specified folder exists
		if folder theFolderName of theOutputFolder exists then
			
			-- Define a variable that references the existing folder
			set theFolder to folder theFolderName of theOutputFolder
			
			-- Create a new folder, if it doesn't exist
		else
			set theFolder to make new folder at theOutputFolder with properties {name:theFolderName}
		end if
		
		-- Open the folder
		open theFolder
	end tell
end checkFolder

on repositionFolderWindow()
	tell application "Finder"
		-- Reposition the folder's window
		set position of window of theFolder to {10, 50}
	end tell
end repositionFolderWindow

Declaring a Global within a Handler

Alternatively to declaring a variable as global at the top level of a script, a variable may be declared global within a handler. In doing so, however, the variable will not become global to the entire script. Rather, it will become global to only the handler itself and the top level of the script.

The following example code declares the variable theFolder as a global variable, but does so within the checkFolder handler. Because of this, the following code will produce an error when the repositionFolderWindow handler is called. This is because the variable theFolder is only global to the handler itself and the top level of the script. The repositionFolderWindow handler does not have access to it.


-- Check for the folder
checkFolder(path to desktop folder, "My Folder")

-- Reposition the folder's window
repositionFolderWindow()

-- Close the folder
tell application "Finder"
	close theFolder
end tell

on checkFolder(theOutputFolder, theFolderName)
	-- Declare the variable as a global
	global theFolder
	
	tell application "Finder"
		-- Determine whether the specified folder exists
		if folder theFolderName of theOutputFolder exists then
			
			-- Define a variable that references the existing folder
			set theFolder to folder theFolderName of theOutputFolder
			
			-- Create a new folder, if it doesn't exist
		else
			set theFolder to make new folder at theOutputFolder with properties {name:theFolderName}
		end if
		
		-- Open the folder
		open theFolder
	end tell
end checkFolder

on repositionFolderWindow()
	tell application "Finder"
		-- Reposition the folder's window
		set position of window of theFolder to {10, 50}
	end tell
end repositionFolderWindow

If the code above is changed to the following, to eliminate referencing the variable theFolder within the repositionFolderWindow handler, it will now execute successfully.


-- Check for the folder
checkFolder(path to desktop folder, "My Folder")

-- Reposition the folder's window
tell application "Finder"
	set position of window of theFolder to {10, 50}
	
	-- Close the folder
	close theFolder
end tell

on checkFolder(theOutputFolder, theFolderName)
	-- Declare the variable as a global
	global theFolder
	
	tell application "Finder"
		-- Determine whether the specified folder exists
		if folder theFolderName of theOutputFolder exists then
			
			-- Define a variable that references the existing folder
			set theFolder to folder theFolderName of theOutputFolder
			
			-- Create a new folder, if it doesn't exist
		else
			set theFolder to make new folder at theOutputFolder with properties {name:theFolderName}
		end if
		
		-- Open the folder
		open theFolder
	end tell
end checkFolder

Return Values

In last month’s column, we discussed how to pass values into a handler via parameters. It is also possible to return a value from a handler, to the code that is calling the handler. This is done by using the return command, followed by the value you wish to return. For example:


-- Check for the folder
set theFolder to checkFolder(path to desktop folder, "My Folder")

tell application "Finder"
	-- Reposition the folder's window
	set position of window of theFolder to {10, 50}
	
	-- Close the folder
	close theFolder
end tell

on checkFolder(theOutputFolder, theFolderName)
	tell application "Finder"
		-- Determine whether the specified folder exists
		if folder theFolderName of theOutputFolder exists then
			
			-- Define a variable that references the existing folder
			set theFolder to folder theFolderName of theOutputFolder
			
			-- Create a new folder, if it doesn't exist
		else
			set theFolder to make new folder at theOutputFolder with properties {name:theFolderName}
		end if
		
		-- Open the folder
		open theFolder
	end tell
	
	-- Return the folder reference to the top level of the script
	return theFolder
end checkFolder

In the example code above, the value of the variable theFolder is returned by the checkFolder handler. When the handler is called at the root level of the script, the value returned from the handler is captured in another variable named theFolder, which is then referenced to reposition the folder’s window and close the folder.

The return command does not need to be used at the end of a handler to return a value to the code calling the handler. It can be used anywhere within a handler. For example, in the following code, the checkFolder handler will return a value of false if the specified folder already exists, but a reference to the folder if it does not exist and is created. An if/then statement is then utilized to perform further processing if the value returned from the handler is a reference to a newly created folder.


-- Check for the folder
set theFolder to checkFolder(path to desktop folder, "My Folder")

-- If the folder has just been created
if theFolder is not equal to false then
	tell application "Finder"
		
		-- Reposition the folder's window
		set position of window of theFolder to {10, 50}
		
		-- Close the folder
		close theFolder
	end tell
end if

on checkFolder(theOutputFolder, theFolderName)
	tell application "Finder"
		-- Determine whether the specified folder exists
		if folder theFolderName of theOutputFolder exists then
			
			-- Return a value of false if the folder already exists
			return false
			
			-- Create a new folder, if it doesn't exist
		else
			set theFolder to make new folder at theOutputFolder with properties {name:theFolderName}
			
			-- Open the folder
			open theFolder
			
			-- Return the folder reference to the top level of the script
			return theFolder
		end if
	end tell
end checkFolder

The return command can also be used, without specifying a value, to simply exit the handler and return to the calling code, without returning a value. For example, in the following code, the checkFolder handler will return to the top level of the script, and cease executing if the specified folder already exists.


-- Check for the folder
checkFolder(path to desktop folder, "My Folder")

on checkFolder(theOutputFolder, theFolderName)
	tell application "Finder"
		-- If the folder exists already, then stop
		if folder theFolderName of theOutputFolder exists then return
		
		-- Create the folder
		set theFolder to make new folder at theOutputFolder with properties {name:theFolderName}
		
		-- Open the folder
		open theFolder
		
		-- Reposition the folder's window
		set position of window of theFolder to {10, 50}
		
		-- Close the folder
		close theFolder
	end tell
end checkFolder

Calling Handlers from within Application Tell Statements

In last month’s column, we discussed calling handlers throughout your script. However, one thing that I neglected to mention was that you may encounter an error if you attempt to call a handler from within an application tell statement. For example, the following code will produce the error “Finder got an error: Can’t continue displayError.


set theFolderName to "My Folder"
set theOutputFolder to path to desktop folder
tell application "Finder"
	-- Determine whether the specified folder exists
	if folder theFolderName of theOutputFolder exists then
		
		-- Display an error message, if the folder already exists
		displayError("Could not create the folder because it already exists.")
		
		-- Create a new folder, if it doesn't exist
	else
		set theFolder to make new folder at theOutputFolder with properties {name:theFolderName}
	end if
	
	-- Open the folder
	open theFolder
end tell

on displayError(theError)
	-- Display an error message
	display dialog theError buttons {"Cancel"} default button "Cancel"
end displayError

The reason that this error occurs is because the Finder has no idea what displayError means. This terminology isn’t in the Finder’s AppleScript dictionary. Therefore, it produces an error. That said, it is possible to make this code work. To do so, you must provide information that indicates where the displayError handler exists. Since the handler exists within the script itself, you can use the predefined variable me to indicate where the Finder should look for the handler. For example:


set theFolderName to "My Folder"
set theOutputFolder to path to desktop folder
tell application "Finder"
	-- Determine whether the specified folder exists
	if folder theFolderName of theOutputFolder exists then
		
		-- Display an error message, if the folder already exists
		displayError("Could not create the folder because it already exists.") of me
		
		-- Create a new folder, if it doesn't exist
	else
		set theFolder to make new folder at theOutputFolder with properties {name:theFolderName}
	end if
	
	-- Open the folder
	open theFolder
end tell


on displayError(theError)
	-- Display an error message
	display dialog theError buttons {"Cancel"} default button "Cancel"
end displayError

This code will now run successfully.

In Conclusion

We have now covered a lot of information with regard to using handlers in your scripts, and hopefully you are beginning to use them to make your scripts more modular and your script writing more efficient. However, there is still more to discuss. In my next column, we will continue discussing handlers, and will focus on command handlers. Then, it’s on to AppleScript Studio!

Until next time… just script it!

Link to part 3: http://www.macscripter.net/viewtopic.php?id=24743

1 Like