Your prescription for increased productivity and profitability
Many times when revisiting a script that you may have written awhile ago, you may wonder why you decided to use the logic that you did. That’s the beauty of working with InDesign: There’s so many different ways you can solve a problem.
One script that I have considered essential over the years is one that divides a selected page item into a grid of same-styled items. It does this by duplicating the selected item. One optional functionality that I often take advantage of is the ability to name and incrementally number the items created. The changes I have made to my original DivideIt script are minor but makes the code a little more organized. It’s a fairly complex script so we will take it a step at a time.
Determine if a document is open and a page item is selected.
set selItem to getSelectItem() (*returns reference to item selected and its parent page. Throws error if no document or no valid page selection*) on getSelectItem() set errStr to "Requires document with selected page item to run this script" tell application "Adobe InDesign CC 2019" if (exists document 1) then set docRef to document 1 else error errStr end if set objTypes to {rectangle, oval, polygon, text frame} set selList to selection if selList is not {} and class of item 1 of selList is in objTypes then set selItem to item 1 of selList else error errStr end if end tell return selItem end getSelectItem
Notice that although there are error faults written into the getSelection handler, there is no trap set to catch the error. We will wait to add this later when most of the script is written. We will start with calculations and perform the first duplication.
Start Main Functionality
Place values for variables at the top of the script. These will be replaced when the main functionality (divideItem) is completed and tested.
--measurements are in points set numRows to 3 set numCols to 4 set gut to 12 set gap to 6 set doInc to true set nameBase to "Img" set nameList to {nameBase, doInc} set selItem to getSelectItem() divideItem (selItem, numRows, numCols, gut, gap, nameList) (*Gets measurements for dividing selected item using duplicate. *) on divideItem(pageRef, selItem, numRows, numCols, gut, gap, nameList) set cntr to 1 tell application "Adobe InDesign CC 2019" set measurement unit of script preferences to points copy geometric bounds of selItem to {y0, x0, y1, x1} set itemWid to x1 - x0 set itemHgt to y1 - y0 set colWid to (itemWid - (numCols - 1) * gut) / numCols set rowHgt to (itemHgt - (numRows - 1) * gap) / numRows set ny0 to y0 set nx0 to x0 set lastItem to selItem set thisItem to duplicate lastItem set nx1 to nx0 + colWid set geometric bounds of selItem to {ny0 + colWid, nx0, ny1, nx1} end tell end divideItem
Notice that the measurement unit for script preferences is set to points. This makes sure that all calclations in the script will be in that unit without disturbing the measurement units set for the document.
The column width (colWid) and row height (rowHgt) for the grid are calculated by multiplying the number of items in the column/row minus 1 by the gutter/gap measurement. This is then subtracted from the column/row width/height. This is then divided by the number of units in the column/row.
Make sure to add the getSelectItem() handler to your script. Save your document before testing your script so you can revert it (File > Revert) to its original after testing . You can now run the script. The reference to the selected item (selItem) is preserved so we can delete it when the script finishes. (We will leave it intact at this point.)
With the script working as anticipated, add the following repeat loop after the line that sets lastItem to selItem:
repeat with i from 1 to numRows set ny1 to ny0 + rowHgt repeat with j from 1 to numCols set thisItem to duplicate lastItem set nx1 to nx0 + colWid set geometric bounds of thisItem to {ny0, nx0, ny1, nx1} set lastItem to thisItem set nx0 to nx1 + gut end repeat set nx0 to x0 set ny0 to ny1 + gap end repeat
When you test the script with this addition you should have 3 rows and 4 columns of page items. Notice how lastItem becomes the reference to what was thisItem each time through the loop. Lastly, value for the lest coordinate is set to the original x0 value and the value for the top y coordinate (ny0) is set to what was the bottom coordinate of the previous item (ny1) plus the value for gap.
If the value for nameList is not an empty list, the script will need to name each item as part of the loop. This will be done with the following if statements.
if length of nameList > 0 then set baseName to item 1 of nameList if item 2 of nameList is true then set baseName to baseName & "_" & cntr end if set name of thisItem to baseName set cntr to cntr + 1 end if
Add the if statements to the script right after the geometric bounds of thisItem are set. (Now you see why the variable cntr was established at the beginning of the handler.)
Add the getSelectItem() handler to the bottom of the script, The rest should now read as follows :
set numRows to 3 set numCols to 4 set gut to 12 set gap to 18 set doInc to true set nameBase to "Img" set nameList to {nameBase, doInc} try set selItem to getSelectItem() divideItem(pageRef, selItem, numRows, numCols, gut, gap, nameList) tell application "Adobe InDesign CC 2019" delete selItem end tell on error errStr activate display alert "Error: " & errStr end try pageRef (*Divides selected item using duplicate. *) on divideItem(selItem, numRows, numCols, gut, gap, nameList) set cntr to 0 tell application "Adobe InDesign CC 2019" set measurement unit of script preferences to points copy geometric bounds of selItem to {y0, x0, y1, x1} set itemWid to x1 - x0 set itemHgt to y1 - y0 set colWid to (itemWid - (numCols - 1) * gut) / numCols set rowHgt to (itemHgt - (numRows - 1) * gap) / numRows set ny0 to y0 set nx0 to x0 set lastItem to selItem repeat with i from 1 to numRows set ny1 to ny0 + rowHgt repeat with j from 1 to numCols set thisItem to duplicate lastItem set nx1 to nx0 + colWid set geometric bounds of thisItem to {ny0, nx0, ny1, nx1} if length of nameList > 0 then set baseName to item 1 of nameList if item 2 of nameList is true then set baseName to baseName & "_" & cntr end if set name of thisItem to baseName set cntr to cntr + 1 end if set lastItem to thisItem set nx0 to nx1 + gut end repeat set nx0 to x0 set ny0 to ny1 + gap end repeat end tell end divideItem
Hopefully, you should be getting a somewhat comfortable in writing scripts as modules (making them easier to write and certainly much easier to test and debug).
Of course you will want to add a custom dialog to get the values for creating the grid from the user. You will want to create this as a separate script and add it once thoroughly tested. To call the handler and set values change the lines between try and on error at the top of the script to read:
set userResponse to userDialog("NameOfDialog")
The handler for creating the dialog is a little wordy but should be easy to follow:
(*Returns user-entered values for rows, columns, gutter, gap, and optional name and increment*) on userDialog(dlgName) tell application "Adobe InDesign CC 2019" activate set origLevel to user interaction level of script preferences set user interaction level of script preferences to interact with all set labelWid to 72 set myDlg to make dialog with properties {name:dlgName} tell myDlg set colRef to make dialog column tell colRef tell (make dialog row) make static text with properties {static label:"Enter values for grid using point units", min width:272, static alignment:center align} end tell set myBdr to make border panel tell myBdr tell (make dialog column) make static text with properties {static label:"Number Columns: ", min width:labelWid} make static text with properties {static label:"Number Rows: ", min width:labelWid} make static text with properties {static label:"Gutter Width: ", min width:labelWid} make static text with properties {static label:"Gap Height: ", min width:labelWid} end tell tell (make dialog column) set colField to make integer editbox with properties {edit value:2} set rowField to make integer editbox with properties {edit value:2} set gutterField to make measurement editbox with properties {edit value:12, edit units:points} set gapField to make measurement editbox with properties {edit value:12, edit units:points} end tell end tell set enable1 to make enabling group with properties {static label:"Add Names", checked state:false} tell enable1 tell (make dialog column) tell (make dialog row) make static text with properties {static label:"Name Base:", min width:labelWid} set nameField to make text editbox with properties {min width:200, edit contents:"Image"} end tell tell (make dialog row) set checkField to make checkbox control with properties {static label:"Add Increment", checked state:false} end tell end tell end tell end tell end tell set userResponse to show myDlg set valueList to {} set nameList to {} if userResponse then set valueList to {edit value of colField, edit value of rowField, edit value of gutterField, edit value of gapField} if checked state of enable1 then set nameList to {edit contents of nameField, checked state of checkField} end if set end of valueList to nameList end if destroy myDlg set user interaction level of script preferences to origLevel return valueList end tell end userDialog
The script packages the values entered by the user into a list and passes it back to the main script, eliminating the need for setting variables at the top.
Once tested, you can add it to the DivideIt script by changing the statements inside the try/on error statements to the following:
set selItem to getSelectItem() set userResponse to userDialog("NameOfDialog") if userResponse is not {} then copy userResponse to {numRows, numCols, gut, gap, nameList} divideItem(selItem, numRows, numCols, gut, gap, nameList) tell application "Adobe InDesign CC 2019" delete selItem end tell else error ("User Cancelled dialog") end if
Now that you have added the custom dialog you no longer need to have the values for variables defined at the top of the script.
Disclaimer:
Scripts provided are for demonstration and educational purposes. No representation is made as to their accuracy or completeness. Readers are advised to use the code at their own risk.