2009-07-17

Move an Object Any Direction in Any Measurement System

Works regardless of the document's measurement system

One problem often encountered when manipulating items on Adobe InDesign pages is knowing what the current page's or document's measurement system. If you asked something to move '1', you don't know if you are moving 1 pica, 1 inch, or 1 centimeter. To make matters worse, a horizontal movement might happen in one measurement system and vertical adjustments in another measurement system.

This function solves that by allowing you to specify the distance in any measurement system. Just supply the 'unit' parameter a string such as 'picas', 'inches', 'points', 'cm', etc.

The function is long because it has loads of error checking. But it works, and that is all that matters.

//
function moveObjectBy ( o , down , right , unit ) {
//-------------------------------------------------------------------------
//-- M O V E O B J E C T B Y
//-------------------------------------------------------------------------
//-- Generic: Yes for Adobe InDesign CS3
//-------------------------------------------------------------------------
//-- Purpse: To move the passed object down and to the right by the
//-- specified amount in the passed unit values.
//-- The significant thing here is that you do not need to worry about
//-- the current measurment system in use on the Adobe InDesign page.
//-- Another benifit of this routine is that some special objects
//-- can be constructed via plug-ins which prevent them from having
//-- thier bounds manipulated. Ads on MediaSpan jazbox pages are
//-- one of these types of objects.
//-------------------------------------------------------------------------
//-- Parameters: 4
//-- o: The Adobe InDesign object to move. There are many things
//-- that can be moved. Any page item ( text frame, graphic
//-- frame, unassigned frames, graphic line, group, etc. If
//-- it can be modified using controls in the Object menu in
//-- Adobe InDesign, likely it can me moved by this function.
//-- down: The amount to move the object down. If you want to move
//-- the object up, then use a negative value.
//-- right: The amount to move the object to the right. Again, use
//-- a negative value to move the item to the left.
//-- unit: A string. Can be any of a large number of choices.
//-- For example: 'pica', 'point', 'mm', 'centimeter', 'inches'
//-- or 'pc', 'pt', 'millimeters', 'cm', 'i'
//-------------------------------------------------------------------------
//-- Returns: True if successful. False if something prevented the item
//-- from moving. For example, locked objects cannot be moved.
//-------------------------------------------------------------------------
//-- Sample Use:
//~ var s = app.selection[0]
//~ moveObjectBy ( s , 0 , 2.54 , 'cm' ) // right 1 inch
//~ moveObjectBy ( s , 3 , undefined , 'picas' ) // down a half
//~ moveObjectBy ( s , 0 , -1 , 'in' ) // back to the left 1 inch
//~ moveObjectBy ( s , -36 ) // return to the starting place
//-------------------------------------------------------------------------
//-- Written: 2009.07.17 from scratch during flight US377 from EWR to CLT
//-- Written by: Jon S. Winters of electronic publishing support
//-- eps@electronicpublishingsupport.com
//-------------------------------------------------------------------------
//-- Verify there are good numberic values for down and right or set to 0
if ( isNaN ( down ) ) { down = 0 } ;
if ( isNaN ( right ) ) { right = 0 } ;
//-- Assume points (my favorite for scripts) if the unit isn't passed.
if ( unit == undefined ) { var unit = 'points' ; }
//-- Make sure that we passed a lowercase value.
unit = String ( unit ).toLowerCase() ;
try {
//-- Convert to ExtendScript unit values. This is unique to
//-- ExtendScript and is not part of JavaScript or ECMAScript.
//-- Note, there appears to be a bug in how this works. Picas
//-- converts to 'pc' and move won't take that, so back to
//-- picas the string will be converted.
var downUnitsToMove = String ( new UnitValue ( down , unit ) ).replace ( 'pc', 'picas') ;
var rightUnitsToMove = String ( new UnitValue ( right , unit ) ).replace ( 'pc', 'picas') ;
//-- Most objects support a .move method. Try it then return true.
o.move( undefined , [ rightUnitsToMove , downUnitsToMove ] ) ;
return true ;
}
catch (err) {
//-- if any of the conversions to unit values failed or if
//-- the passed object cannot be moved this way, then
//-- return an error.
return false ;
}
}
//

Display Dialog with Radio Buttons of Array Elements

The Final Installment in Selecting an Array Element

Radio Buttons are the third user interface option when it comes to selecting items from a list. Like a Drop Down Menu, a user is only permitted to select a single choice. When the number of items to select from is limited, it is easiest to select from a set of radio buttons.

All three versions of Array Element selection presented have some unique feature. The checkbox version had a method of controlling how many items would appear in a column. That could be incorporated here. This function has the ability to display an optional script version and discusses that in the scripts comments. This version, as do all the others, could actually accept an Adobe InDesign or Adobe InCopy collection. A collection is a superset of an Array object and collections have properties other than .length. For example if you wanted to get the name of all the Master Pages in a document...
var mps = app.documents[0].masterSpreads.everyItem().name
You could also just refer to the name of one item in the collection using the code within the function below. Just read the embedded documentation.


//
function chooseFromList( lst , prmt , dflt ) {
//-------------------------------------------------------------------------
//-- C H O O S E F R O M L I S T
//-------------------------------------------------------------------------
//-- Generic: Yes for any version of Adobe InDesign or Adobe InCopy
//-- that can display a custom dialog.
//-------------------------------------------------------------------------
//-- Purpose: Displays a dialog with a series of radio buttons with
//-- choices passed into 'lst'.
//-------------------------------------------------------------------------
//-- Returns: index of chosen item or -1 if they clicked cancel
//-------------------------------------------------------------------------
//-- Parameters: 3
//-- lst: An array of things that can be coorersed into strings
//-- prmt: a string that will be used to instruct the user what they
//-- should select. Something like ; 'Select a Paragraph Style:'
//-- dflt: a
//-------------------------------------------------------------------------
//-- Sample Use:
//~ var a = [2, 4, 6, 8, 'who', 'do', 'we', 'appriciate']
//~ var daChoice = chooseFromList ( a , 'Select Something:' )
//~ if ( daChoice != -1 ) alert ( 'You picked: ' + a[daChoice] )
//-------------------------------------------------------------------------
//-- Written by Jon S. Winters on 2008.12.24
//-- Edited: 2009.07.17 to provide better comments.
//-- eps@electronicpublishingsupport.com
//-------------------------------------------------------------------------
//-- Version 1.1: Force Adobe InDesign to display dialogs. Without the
//-- line below the dialog may not appear on some systems.
app.scriptPreferences.userInteractionLevel = UserInteractionLevels.INTERACT_WITH_ALL ;
//-- Assign a default default choice if one isn't passed;
dflt = dflt || 0 ;
//-- If the default value is less than 0, reset to zero.
//-- This was initially done because this function was created for
//-- a script where the user would get the dialog multiple times
//-- throughout the course of the day and the persistent
//-- target engine would pass as default the last choice
//-- but if a user cancels, then the last choice will be -1.
dflt = (dflt <>
//-- Standard way to make a new dialog with a prompt and a cancel button.
var listDialog = app.dialogs.add({canCancel:true, name:prmt}) ;
//-- Setup the buttons array which will be filled as the dialog is
//-- constructed.
var buttons = new Array () ;
//-- Go through the processes of setting up the areas of the dialog
//-- consult the Adobe Documentaion if you really want to know
//-- what is going on here. Otherwise, just use it.
with (listDialog) {
with (dialogColumns.add()) {
var userChoice = radiobuttonGroups.add()
with ( userChoice ) {
//-- Loop through every item of the list. Note, if the list
//-- is really a collection of Adobe InDesign or Adobe
//-- InCopy objects, they will have .name properties.
//-- Thus, you can add .name in the staticLabel. See
//-- the commented out version.
for ( var loopIndex = 0 ; loopIndex <>
//-- Below for standard Array of Elements that can be
//-- displayed as a string. Note the conversion to
//-- the String object. This will be necessary for
//-- list elements that don't have strings.
buttons[loopIndex] = radiobuttonControls.add( {staticLabel:String(lst[loopIndex]) } );
//-- Below version will allow you to see the name of a item
//-- in a collection.
//~ buttons[loopIndex] = radiobuttonControls.add( {staticLabel:lst[loopIndex].name } );
} //-- end of for loop
//-- Note, the next thing is interesting. We are currently in
//-- a with (radionbuttonGroup) statement. That has a
//-- selectedButton property. And now that all the
//-- buttons have been added, the selected buttton
//-- can be set to the default value.
selectedButton = dflt ;
} ///-- end of with radiobuttonGroups
//
//-- This next thing is also interesting. ExtendScript, like
//-- ECMAScript and Javascript, has a global object. That
//-- object is referenced as
//-- this
//-- some of my scripts set a global 'scriptVersion'
//-- if the scriptVersion exists, then the dialog will get
//-- the text of that scriptVersion
if (this.scriptVersion) {
with (dialogRows.add() ) {
with (dialogRows.add() ) {
staticTexts.add({staticLabel:String(this.scriptVersion)}) ;
}
}
}
//
}
}
//-- Show the dialog, and wait for them to click OK or Cancel.
var listResult = listDialog.show() ;

//-- return the selected list index unless they clicked Cancel.
return ( listResult ? userChoice.selectedButton : -1 ) ;
}
//

2009-07-16

Display Dialog with Dropdown Menu of Array Elements

Another way to display an array
(How's that for alliteration?)
A recent post dealt with displaying a dialog box with a checkbox for every array elements. That works if you want to select more than a single entry in the array. But many times you only need the option to choose a single array element. In that case the GUI should include a dropdown menu or a set of radio buttons. This generic ExtendScript function takes an array of elements and a prompt string and constructs and displays a dialog box allowing the user to select an element of the array.
The function returns not the array element but the index in the array of that element.
Note, if you want the items sorted then use the .sort() method on the array prior to passing it to the function.
If the user doesn't select an array element or clicks cancel the function returns a -1 value. Note, in JavaScript an array can't be referenced with a negative index. But in ExtendScript, if you have a collection, instead of a true array, then -1 is the last item in the ExtendScript collection.
This function was written for a script that allows the user to select text in Adobe InDesign or Adobe InCopy and toggle the Bold or Italic font styles with a keyboard shortcut. The script is quite nice because nothing is hard coded. Instead a site installs the pieces, assigns the keyboard shortcuts, and then uses it. The first time they use it, it displays a dialog asking what Character Style to apply to the selected text. Any future time the same type of text is selected, the script will know what Character Style to apply. It works great. That script requires Character Styles when a normal Bold or Italic font style won't suffice such as when you not only apply a bold, but you actually switch fonts. This script is for sale to any site that needs it, just send a e-mail to eps@electronicpublishingsupport.com
A separate link to toggleStyle() will be setup eventually.

//
function selectArrayElementViaDropdown(lst,prmt) {
//-----------------------------------------------------------
//-- S E L E C T A R R A Y E L E M E N T F R O M D R O P D O W N
//-----------------------------------------------------------
//-- Generic: Yes for current versions of Adobe Products
//-- that support custom dialog boxes
//-----------------------------------------------------------
//-- Purpose: Displays a dialog a single dropdown menu of
//-- choices passed into 'lst'.
//-----------------------------------------------------------
//-- Parameters: 2
//-- lst: An Array of items to display to the user
//-- prmt: A string prompt to ask the users what to do.
//-----------------------------------------------------------
//-- Returns: index of chosen item or -1 if they clicked cancel
//-----------------------------------------------------------
//-- Calls: Nothing.
//-----------------------------------------------------------
//-- Sample Use:
//~ var pStyleNames = ['Body bj', 'Body Ragged brr', 'Body Wire bw']
//~ var selectedStyle = selectArrayElementViaDropdown ( pStyleNames , 'Choose the Paragraph Style to apply to the body of this story' ) ;
//~ if ( selectedStyle >= 0 ) {
//~ var theStyleNameToUse = pStyleNames[selectedStyle]
//~ // Do something with the style name here
//~
//~
//~ }// End of the if block
//-----------------------------------------------------------
//-- Written sometime in 2009 by Jon S. Winters
//-- eps@electronicpublishingsupport.com
//-----------------------------------------------------------
//
var listDialog = app.dialogs.add({canCancel:true});
with (listDialog) {
with (dialogColumns.add()) {
with (dialogRows.add() ) {
// show prompt
staticTexts.add({staticLabel:prmt});
}
with (dialogRows.add() ) {
//show the list
var userChoice = dropdowns.add({stringList:lst})
}
}
}
//-- Show the dialog
var listResult = listDialog.show() ;
//
if (listResult) {
return userChoice.selectedIndex ;
}
else {
return -1 ;
}
}
//

2009-07-15

Unlock an Adobe InDesign Object

Short code, could be shorter...

Here is a really simple function to unlock an Adobe InDesign object via script. It is a case where the comments far outweigh the code. The code itself could loose two line by dropping the else clause. And if you collapse the if block it too could be on one line. But it is easier to read as it is. The key here is not unlocking the object directly (because it won't work), but first trying to see if the parent object is also locked. When the locked parents are unlocked, the object itself will be unlocked. That is the way it works in Adobe InDesign -- A departure from QuarkXPress thinking.


function unlock(o) {
//-------------------------------------------------------------------------
//-- U N L O C K
//-------------------------------------------------------------------------
//-- Generic: Yes for Most versions of Adobe InDesign
//-------------------------------------------------------------------------
//-- Purpose: To unlock position of a locked object. If successful the
//-- function returns false to indicate the locked status of the
//-- object. The function is recursive, and calls itself on the
//-- parent of the object because in Adobe InDesign, if the an
//-- object of group is locked, the group is locked. Thus, you have
//-- to unlock the group that contains the object to unlock the
//-- object itself.
//-------------------------------------------------------------------------
//-- Parameters: o: The object to unlock. Very generic as to what that
//-- object actually is.
//-------------------------------------------------------------------------
//-- Returns:
//-- False if the object is now unlocked.
//-- True if there was an error and the item remains locked or cannot
//-- be unlocked because there is no locked or unlocked option.
//-------------------------------------------------------------------------
//-- Calls: Itself. The function is recursive.
//-------------------------------------------------------------------------
//-- Sample Use:
//~ var A = app.selection[0] ;
//~ unlock(A) ;
//-------------------------------------------------------------------------
//-- Written: 2009.06.13 -- HA
//-- Written by Jon S. Winters of electronic publishing support
//-- eps@electronicpublishingsupport.com
//-------------------------------------------------------------------------

//-- If an Adobe InDesign's _parent_ object can be locked it will have a
//-- property called locked. If it has that property, then call this
//-- same function recursively on the _parent_ of the object.
if ( o.parent.hasOwnProperty ('locked') ) {
return ( unlock ( o.parent ) ) ;
}
else {
//-- The else really isn't necessary in this case, but it makes
//-- the function easier to read...
//-- If here, then try to unlock the object itself and return
//-- false, otherwsise it could generate an error becuase it
//-- can't be unlocked and thus return true.
try { return ( o.locked = false ) }
catch ( err ) { return true }
}
}
//

2009-07-14

Listing Paragraph Styles

I'm at a site this week that currently has 947 Paragraph Styles. I needed a list of them. Here is an ExtendScript to list them into a selected text frame in an Adobe InDesign page. This was a quick hack, so no verbose comments.
Note the two different ways of referencing the document. The first method uses a function posted here. Delete that line if you don't want to use it (no real need in this case).

Oh, and here with the 900+ Paragraph Styles, we had to add 7 more broadsheet pages to show the styles.

try {
//see if there is a parentStory. If so, then fill it with the names of the styles
//-- The next line errors if there isn't a proper selection
var storyRef = app.selection[0].parentStory.textContainers[0].parentStory ;

//-- Get a valid reference to the document holding the story
var docRef = returnDocumentReference ( storyRef ) ;
//OR use
var docRef = app.documents[0];

//-- Call the function to add the styles.
listParagraphStyles ( storyRef , docRef )
}
catch (err) {
//-- The next line quits the script.
exit() ;
}
//
function listParagraphStyles ( storyRef , docRef ) {

//-- Clear the contents of the the frame
storyRef.contents = '\r' ;

//-- Get an array of all the Paragraph Styles
var allPS = docRef.allParagraphStyles ;
var numPS = allPS.length ;

//-- Loop through the styles adding the name of the style and applying that style to the story
for ( var pIndex = 0 ; pIndex < numPS ; pIndex++ ) {
storyRef.paragraphs[pIndex].contents = allPS[pIndex].name + '\r ' ; //note the space after the return
//-- Remove the next line if you don't want to see the style name formatted with that
Paragraph Style.
storyRef.paragraphs[pIndex].appliedParagraphStyle = allPS[pIndex] ;
}
}
//

2009-07-13

Display Dialog of Checkboxes of Array Elements

For a project to print or export Adobe InDesign pages to PostScript Files, PDFs, or EPS files to fixed folder locations with fixed output settings (EPS settings, Print Styles, PDF Export Styles), I needed a dialog to display the pages in active document so the user could choose to exclude certain pages from the output. The reason being if you are working with a multi-page Adobe InDesign document, you might need to output some pages before the rest are 'finished'.
So my script to output these pages to a pre-specified folder on a file server (controlled by a site specific XML file), needed a way to control which pages were going to print.
The code snippet looks like this...

//-- Limit which pages to print if the method details indicates to
if ( limitOutputToSpecificPages ) {
var arrayToCheck = app.documents[0].pages.everyItem().name ;
var prmt = 'Pages:' ;
var pagesToOutput = selectListOptions( arrayToCheck , prmt , true )
}
//
//-- Determine how to handle the naming of the extra pages
//-- Loop through the pages
for (var pgIndex = numPages - 1 ; pgIndex >= 0 ; pgIndex-- ) {

//-- Deny output of pages if the user didn't check to output them
if ( limitOutputToSpecificPages && ( ! pagesToOutput[pgIndex] ) ) {
continue ;
}
//
//-- HANDLE THE OUTPUT HERE


}// end of for loop

Of course that snippet leaves out a lot of details, but basically if a value (limitOutputToSpecificPages) says to limit to specific pages, use a function (below) to display a dialog of page numbers with check boxes. The function (below) returns an array of true or false values.
And if the value is false, then skip the rest of the inner part of the for loop (the continue causes teh for loop to not finish, but to go back and check for the end point and increment the loop index)


//
function selectArrayElementsViaCheckboxes(lst,prmt,dflt,colLimit) {
//-------------------------------------------------------------------------
//-- S E L E C T A R R A Y E L E M E N T S V I A C H E C L B
O X E S
//-------------------------------------------------------------------------
//-- Generic: Yes! Should work with all current versions of Adobe
Products
//-- that support custom dialog boxes.
//-------------------------------------------------------------------------
//-- Purpose: Displays a dialog with a series of checkboxes with
//-- choices passed into 'lst'.
//-------------------------------------------------------------------------
//-- Parameters: 4
//-- lst: The array whose contents will be displayed to the user
//-- prmt: A string to be used as a prompt
//-- dflt: (optional) A boolean indicating if ALL the checkboxes
//-- should initially be selected (checked) or deselected
//-- (unchecked). _ALL_!
//-- colLimit: (optional) The maximum number of entries to place
//-- in any one column.
//-------------------------------------------------------------------------
//-- Returns: an array the the same length of the of chosen items. If
the
//-- items are selected as a result of the checkbox items being
//-- checked, the array will be set to true, else false.
//-- If the user cancels the dialog, then the returned array is []
//-------------------------------------------------------------------------
//-- Calls: Nothing.
//-------------------------------------------------------------------------
//-- Sample Use:
//-- var sampleArray = ['extreme', 'fantastic', 'great',
'monstrous', 'monumental', 'prodigious', 'stupendous', 'tremendous']
//-- var chosenItems = selectArrayElementsViaCheckboxes
( sampleArray , 'Select the desired words:' , true , 4 )
//-- for ( i = 0 ; i < chosenItems.length ; i++ ) {
//-- if ( chosenItems[i] ) {
//-- $.writeln('User selected: ' + sampleArray[i] ) ;
//-- }
//-- }
//-------------------------------------------------------------------------
//-- Written by Jon S. Winters on 2008.12.24
//-- Edited: 2009.07.13 onboard flight to Charlotee, NC
//-- eps@electronicpublishingsupport.com
//-------------------------------------------------------------------------

//-- Force Adobe InDesign to display dialogs. Without the
//-- line below the dilaog may not appear on some systems.
app.scriptPreferences.userInteractionLevel =
UserInteractionLevels.INTERACT_WITH_ALL ;

//-- Assign a default default choice if one isn't passed;
//-- This should either be true or false
dflt = ( dflt == undefined ) ? true : Boolean ( dflt ) ;
//
//-- Verify that there is a limit to the number of items in a column.
if ( ! colLimit ) { colLimit = 10 ; }

//-- create a new array of user responses to return
var userChoices = new Array () ;

var listDialog = app.dialogs.add({canCancel:true}) ;
var buttons = new Array () ;

with (listDialog) {
with (dialogColumns.add()) {
with ( borderPanels.add() ) {
with ( dialogColumns.add() ) {
staticTexts.add({staticLabel:prmt});
with (dialogRows.add()) {
//-- loop thorugh the passed list and create the checkbox as
well as the array to return
for ( var listIndex = 0 ; listIndex < lst.length ; listIndex++ ) {

if ( listIndex % colLimit == 0 ) {
var Col = dialogColumns.add();
}
with ( Col ) {
userChoices[listIndex] =
( checkboxControls
.add({staticLabel:String(lst[listIndex]),checkedState:dflt})) ;
}
}
}
}
}
}
}
//-- Show the dialog
var listResult = listDialog.show() ;

if ( listResult ) {
var returnArray = new Array () ;
//-- loop the result and rebuild the results

for ( var listIndex = 0 ; listIndex < lst.length ; listIndex++ ) {
returnArray.push ( userChoices[listIndex].checkedState ) ;
}
//-- return the selected list index unless they
return returnArray ;
}
else {
//-- User clicked Cancel -- return an empty array
return [] ;
}
}
//