SELECTION KEY OBJECT

In our previous post we touched on a problem that exists with AppleScript. This has to do with align and distribute when there is a selection key object.

So what is a selection key object? It is a member of a selection the user has double-clicked to make it the key.

Selection with selection key object

An AppleScript script can determine if there is a selection key object with the following:

tell application "Adobe InDesign CC 2015"
	set selList to selection
	set selObject to selection key object
end tell

With no selection key object, the result of the above is nothing.

For ExtendScript (JavaScript) the syntax is similar:

var docRef = app.documents.item(0);
   var selList = docRef.selection;
   var selItem = docRef.selectionKeyObject;

With a selection key object defined, an operation such as alignment will use that object as its alignment reference. For instance, should you tell InDesign to align selected objects to the top edge, without a selection key object it will align all selected items to the upper-most page item selected. With a selection key object defined, it will use that object for reference. At least, that is the way it is supposed to work. And it does, if you do it manually using the Align panel (Shift F7).

As part of an AppleScript, it does not. The following script demonstrates. It returns an error “Missing required parameter “reference” for method “align”.

   tell application "Adobe InDesign CC 2015"
	set docRef to document 1
	set selList to selection
	set selObject to selection key object
	tell docRef
		align align distribute items selList align option top edges align distribute bounds key object
	end tell
   end tell

A few hundred attempts to add the reference for the selObject to the align statement just produces frustration.

You can change the statement to use item bounds as the align distribute bounds parameter and work around the problem.

(*For a top-edge alignment, you could use the following*)
tell application "Adobe InDesign CC 2015"
    set docRef to document 1
    set selList to selection
    set selObject to selection key object
    if selObject is not nothing then
	copy geometric bounds of selObject to {y0, x0, y1, x1}
	tell docRef
	    align align distribute items selList align option top edges ¬
            align distribute bounds item bounds
	    copy geometric bounds of selObject to {a0, b0, a1, b1}
	    set ydif to (y0 - a0)
	    move selList by {0, ydif}
	end tell
    end if
end tell

USING EXTENDSCRIPT

This is one place where ExtendScript rules over AppleScript. The syntax is concise, and works without a problem.

Align To Key (ExtendScript)

 try {
   var docRef = app.documents.item(0);
   var selList = app.selection;
   var selItem = docRef.selectionKeyObject;
   if (!!selItem) {
    docRef.align (selList, AlignOptions.TOP_EDGES,
    AlignDistributeBounds.KEY_OBJECT,selItem);
   }
 } catch (e) {
    alert ("Error " + e);
 }

DO SCRIPT (CALLING EXTENDSCRIPT FROM APPLESCRIPT

If you haven’t played with do script before, now is as good of time as ever.

To add the JavaScript to an AppleScript, you first create the script as text.
Then, you use do script to run the script.

For a simple example: The syntax for telling ExtendScript to show an alert message is as follows:

   alert ("Hello world");

Converting the script to text, it would read:

   "alert (\"hello world\")"

Notice that the entire script is enclosed in quotes. Quotes inside of quotes are escaped with a backslash.

To run the script, an AppleScript will read as follows:

   set myScript to "alert (\"Hello World:\")"
   tell application "Adobe InDesign CC 2015"
	do script myScript language javascript
   end tell

For a more elaborate script, the process can pose a little bit of a challenge.
For the ExendScript Align To Key script above, take it step by step:

  1. Copy the entire ExtendScript script into a new AppleScript document.
  2. Change the first line to read as follows:
    set scpt to "try {" & return
  3. In front of each following line add
    set scpt to scpt & "
  4. At the end of each of these lines, add a quote and & return. The second line would read as follows:
    set scpt to scpt & "var docRef = app.documents.item(0);" & return
  5. Make sure you have a quotation in front of each ExtendScript statement and one at the end.
  6. The only quotation mark you need to escape is the one in the error statement. With quotation marks escaped, it will read as follows:
      set scpt to scpt & "alert (\"Error \" + e);"
  7. Run the script and check out the result. It should read exactly as the ExtendScript above.

Now add your script definition (the value for the variable scpt) to a do script method.
Beneath the statements above, add:

   tell application "Adobe InDesign CC 2015"
      do script scpt language javascript
   end tell

With a sample document open and page items selected having a selection key object, run the script.

DO SCRIPT WITH A FILE

If you find yourself having to use do script to call an ExtendScript fairly often, you may consider saving the script as an external file.

Simply write the ExtendScript code as is (see code above) and save it to a file with a .jsx extension. Of course you will want to make sure your script works, so why not open ExtendScript Toolkit and write the script there. Test the script, and when tested, save it to a protected location on your computer. May I suggest keeping a folder of all of your favorite ExtendScripts in a folder named JavaScripts in the User folder for InDesign.

Use InDesign’s Script Preferences to get a path to the folder and use a do script method to access the script from there.

For example, the following example script expects that a file named “Align_ToTop.jsx exists in a folder named JavaScripts inside the User’s Scripts folder. It then uses do script to run the script from within the AppleScript script.

   tell application "Adobe InDesign CC 2015"
	set scriptFolder to (scripts folder of script preferences as string)
	set fileAlias to (scriptFolder & "JavaScripts:Align_ToTop.jsx") as alias
	do script fileAlias language javascript
   end tell 

If there is a document open with a selection key object, the page items selected will align with the top of the selection key object. If no objects are selected, the script errors without a report as to why. For this reason you might want your AppleScript to test for conditions before using do script. The following not only tests for a selection key object, but will throw an error if any other condition fails.

   try
       tell application "Adobe InDesign CC 2015"
	  set selList to selection
	  set selObject to selection key object
	  if selObject is not nothing then
		set scriptFolder to (scripts folder of script preferences as string)
		set fileAlias to (scriptFolder & "JavaScripts:Align_ToTop.jsx") as alias
		do script fileAlias language javascript
	  else
		error "Requires page items selected with selection key object"
	  end if
	end tell
   on error errStr
	activate
	display alert errStr
   end try 

ALIGNMENT OPTIONS

This last script works well with a top edge alignment, but what about the other alignment options? Instead of having a script for each alignment option, pass a number to the JavaScript to indicate the alignment option required. This is passed as part of an arguments array. For the purpose of this script, only one value needs to be passed. Even so, it is passed as a single value array.

   (*Alignment arguments are in order:
   0-top, 1-right, 2-bottom, 3-left, 4-horizontal centers, 5-vertical centers*)
   set myArgs to {4}
   tell application "Adobe InDesign CC 2015"
	set folderRef to (scripts folder of script preferences) as string
	set fileRef to (folderRef & "JavaScripts:Align_Options.jsx") as alias
	activate
	do script fileRef with arguments myArgs language javascript
   end tell 

Add code to test for selection key object as in the script above.

Align_Options.jsx

   /*This script is saved in the User's Scripts Panel for InDesign 
   inside a folder named JavaScripts*/
   var arg1 = arguments[0];
   var docRef = app.documents.item(0);
   var optList = [AlignOptions.TOP_EDGES, AlignOptions.RIGHT_EDGES, AlignOptions.BOTTOM_EDGES, 
   AlignOptions.LEFT_EDGES, AlignOptions.HORIZONTAL_CENTERS, AlignOptions.VERTICAL_CENTERS]
   var aOption = optList[arg1];
   var selList = app.selection;
   var selItem = docRef.selectionKeyObject;
   if (!!selItem) {
      docRef.align (selList, aOption,AlignDistributeBounds.KEY_OBJECT,selItem);
   }

ONWARD AND UPWARD

Think of some other applications where using do script with a JavaScript can help with an automation solution.