Take Some of the Work Out of Creating Custom Dialogs

If you have created scripts having a custom dialog you know that the code can be quite involved. For instance, having to create a row, then a column, and then a user interface element can take up a few lines of code to say the least. You can eliminate some of the code and redundancy by having a few coding tricks up your sleeve. In this discussion we will look at a few:

Nest Tell Statements

Instead of:

   tell application "Adobe InDesign CC 2014"
      set dlgRef to make dialog with properties {name:"Test Dialog", can cancel:true, label:""}
      tell dlgRef
	    set mColumn to make dialog column
	    tell mColumn
		set mRow to make dialog row
	    end tell
	    tell mRow
		make static text with properties {static label:"Name:"}
		set nameField to make text editbox with properties {min width:200}
	    end tell --mRow
	end tell --dlgRef
	set userResponse to show dlgRef
        destroy dlgRef
   end tell

You can write:

   tell application "Adobe InDesign CC 2014"
      set dlgRef to make dialog with properties {name:"Test Dialog", can cancel:true, label:""}
      tell dlgRef
	 tell (make dialog column)
	    tell (make dialog row)
		make static text with properties {static label:"Name:"}
		set nameField to make text editbox with properties {min width:200, edit contents:""}
	    end tell
	 end tell
      end tell
      set userResponse to show dlgRef
      destroy dlgRef
    end tell

Test Dialog

Nesting tell statements in this manner results in a savings of 2 lines of code and having to create 2 additional variables. The only time a variable is created is when an object reference is needed later in the code.

Use Loops

If you have a number of fields that are similar, use a repeat loop to create the fields. To reference the field later in the code, create either a name or object reference as part of its make statement. The following illustrates using a list to hold a reference to the text fields.

   set textFields to {} --list to hold object references
   set fieldList to {"First Name:", "Last Name", "Address:", "Address 2:", "City:", "Zip Code:"}
   tell application "Adobe InDesign CC 2014"
      set dlgRef to make dialog with properties {name:"Test Dialog", can cancel:true, label:""}
      tell dlgRef
	 tell (make dialog column)
	    tell (make dialog row)
		set col1 to make dialog column
		set col2 to make dialog column
	    end tell
	    repeat with i from 1 to length of fieldList
		tell col1
		   make static text with properties {static label:(item i of fieldList)}
		end tell
		tell col2
		   set end of textFields to make text editbox with properties {min width:200, edit contents:""}
		end tell
            end repeat
	 end tell --dialog column
      end tell --dlgRef
      set userResponse to show dlgRef
   destroy dlgRef
end tell 

Writing just this much code will allow you to test and see how your interface will look before you start adding all of the rest.

In running the code above, if the result of the variable userResponse is true the values can be returned using a similar loop construct. Add the following in place of the last two statements in the code above, and test filling in the text fields, and clicking OK.

      if userResponse is true then
         set stringValues to {}
         repeat with eachItem in textFields
            set end of stringValues to edit contents of eachItem
         end repeat
      end if
      destroy dlgRef
   end tell
   stringValues

If you attempted to test the code above and got an error instead of a dialog, the problem may be that script preferences need to be set. To finish off the custom dialog you need to make sure the user interaction level for script preferences is set to interact with all. As with other application preferences, the good neighbor policy is to place values for existing preferences in a variable before changing, then restoring the original value when you are through.

After the tell application statement in the code above, add the following:

   set origPrefs to user interaction level of script preferences
   set user interaction level of script preferences to interact with all

Then after the destroy dlgRef statement in the code above add:

   set user interaction level of script preferences to origPrefs

There is just one more thing you need to do to make the code useable. You need to take care of the problem that will arise if the user presses the Cancel button. Just to test, run the script as it now stands and press the Cancel button.

The best way to handle the problem is to generate an error if the value of userResponse is not true and handle the error in a try/end try block.

It would be nice if we could just add an else statement to the if userResponse is true statement. But we can’t simply because the destroy dlgRef statement needs to occur in either event (userResponse is true or false).

For this you will need to test for the value of userResponse after destroying the dialog. Have the script throw an error if true, or return the dialog result if false.

After the set user interaction level statement at the bottom of the script, add the following:

   --notice the first line is a one line if statement  
   if userResponse is false then error "User Cancelled"
   --if userResponse is true the code gets to the next line
   return stringValues
   --end tell statement follows

Handle the Error

Of course your script now needs to handle the error that is generated if the user cancels. To do this, you can place the custom dialog code in a handler and call the handler from within a try statement.

The script will now read as follows:

   set fieldList to {"First Name:", "Last Name:", "Address:", "Address 2:", "City:", "Zip Code:"}
   set dialogName to "Test Dialog"
   try
      set userResponse to userDialog (dialogName, fieldList, true)
   on error errStr
      activate
      display alert "Error: " & errStr
   end try

   --Handler for custom dialog
   on userDialog (dialogName, fieldList, canCancel)
      set textFields to {}
      --add the rest of the code from above here
   end userDialog 

Test this out to make sure the script works as expected.

 Dialog elements created using repeat loop

Clean Up

Once a dialog is created using the show command, it remains in memory until destroyed. While you have been working with your script, you may have left a few dialogs in memory along the way. To test for this you may want to use the following:

   tell application "Adobe InDesign CC 2014"
	set dialogCount to count of dialogs
	set theName to (name of dialog (dialogCount))
	destroy dialog dialogCount
	set dialogCount to count of dialogs
   end tell
   dialogCount

or simply

   tell application "Adobe InDesign CC 2014"
	set dialogCount to count of dialogs
	if dialogCount > 0 then
		destroy dialogs
	end if
	set dialogCount to count of dialogs --just to make sure
   end tell
   dialogCount --should be 0

Onward and Upward

You may want to improve the appearance of your custom dialog by varying the widths of the text editboxes. This can be done easily by changing the fieldList to a list of lists where the first value in each of its lists is the title for the field, and the second is its width. You could even place default values in the fields by adding a third item to each list inside the fieldList. For dialog elements other than text fields (perhaps a drop down for choosing a State: code), these will need to be created outside of the text field loop.

In our next installation we will look at some AppleScript commands that you may want to incorporate to give even more usability to your custom dialog .