Saturday, December 03, 2005

 

How to begin scripting? (Part 1)

On December 1st, a visitor to the Adobe InDesign Scripting User to User forum asked:

I need [a] script for overset text, but I [am a] novice in scripting. Maybe I can download this?

I interpreted this as a request for help on the issue of how to set about creating a script to solve this problem. In the absence of information about which version of InDesign and which language, I chose to assume that the poster was using the latest version and that a JavaScript solution would meet the need, particularly as that would be cross-platform and again, I had no information about platform. The following is an edited version of my response:

The first step towards scripting is to choose something that you need to do and focus on what it takes to achieve that goal -- for example, if you were to do it manually, what would you do? That helps you decide which parts of the object model you need to be aware of to achieve your goal.

The reason for this is that the object model is a huge beast with everything you ever wanted for scripting anything at all (well, nearly anything) in InDesign. The good news is, you do not need to master every aspect to achieve a focused goal. Indeed, you are likely to need only a small percentage of it. If you think about it, the same is true when using the UI. Newcomers in particular just assume that the application is looking after them in countless ways when they take their first steps to creating a document.

So, in this case, we need to determine if a story or a table cell is overset. In the UI, we get a red + for the story and a red dot for the text cell.

Well, that means that a script needs to look at every story and at every table cell to see if they are overset.

Let's go to the JavaScript section of the InDesign CS2 Reference Guide and look at the properties of a Story. The first thing I'd want to know is: "Does the Story object have a property called 'overset'?"

[Aside: even here, I'm kind of leaping in without introducing at all the concept of a "property" -- does it matter at this stage? Perhaps not, but somewhere I should write about that.]

Using the Story bookmark in the reference guide, I get myself to page 1069 and there's the list of properties. I scroll down (what a long list!) until I get to the "Os" on 1073/74 and while there is no property called overset, there is one called overflows that looks promising.

Yes, it's just what we need. So, if we can find all instances in our document of stories that have the overflow property set to true, then we've found the overset stories. For the moment, let's set aside the issue of how do we inform the user that an overset story has been found and focus on the code we need to find an instance.

So, stories live in documents. Let's take a look at the document object and see what it has in the way of properties. Click the bookmark and we're on page 679. Scroll down to 683 and yes indeedy a document has a property called stories. OK, so how do we refer to the document? For that we need to know what the parent of a document is (or could be).

Unhelpfully, on page 682, the definition of the parent property reads: "The parent of the object." Well -- duh! So we'll have to use our intuition (or preknowledge). There's another object call Application. It's top of the tree (so to speak) and most high-level objects like documents have it as a parent. Another bookmark and another click and we're on page 567.

Before we storm off to find the documents property, let's spend a moment contemplating the very first property: activeDocument. Hey, that sounds useful. I think we're ready to start writing a script.

If we did any reading of the documentation, we know that the way to refer to the application in InDesign is by using the built-in object named app. So, let's make a reference to the active document:
myDoc = app.activeDocument;
Hey, but what if you try to run the script with no documents open? Which is the active document then? Well, there isn't one and you'd expect an error if you ran this little one-line script.

You'd be disappointed. JavaScript doesn't work that way. It creates a reference to a null document in this case. Only if you try to use myDoc will you get an error. Often, this doesn't make a lot of difference because usually you create a reference like this and then immediately use it. But not always, so it is vital to understand this aspect of JavaScript because otherwise you'll be looking for bugs in all the wrong places.

There's two ways to deal with this particular issue: 1. check to see if there are any open documents before setting myDoc, or 2. trap the error when you do use myDoc. Choice 1 is probably the better way to go and that's what I'll use, but let me at least show you have to do it the other way because there are times when this comes in handy:
myDoc = app.activeDocument; 
try {
myName = myDoc.name;
} catch (e) {
alert("Got an error: " + e);
exit();
}
alert("The active document is named: " + myName)
OK, that's a complete script. What to do with it? If you're using CS2, copy it to the clipboard, open ExtendScript Toolkit (ESTK for short) which you'll find in your utilities folder in a folder named Adobe Utilities (at least, that's where it is on a Mac). Make a New script from the File menu and paste this script into it.

Now we could save the script if we wanted to but it's hardly worth it at this stage, let's just run it from ESTK. First, make sure that the target in the drop-down list at top left is set to InDesign CS2 and if so, click the Run button above the script (it's the left-most of the six buttons up there).

Depending on whether or not you have a document open, you'll get one or other of the two alerts.

So what's the better way to do it? Well, normally for this particular issue (is there an active document), I use the documents property of the application, like this:
if (app.documents.length == 0) { 
alert("There are no open documents.");
exit();
}
myDoc = app.activeDocument;
Now we know that myDoc is pointing at a real document.

There's an important point buried in this that might not be obvious to the newcomer. As the application's documents property is properly described on page 568, it refers to all open documents, not all the documents on your computer. So, checking the length of that property tells you how many are indeed open. As long as the answer isn't zero, we're in business.

So, now we want to find the overflows property of all the stories of myDoc. There's a way of doing this that sadly didn't make it into the reference guide and that is to use the everyItem() method. You use it like this:
myOsetList = myDoc.stories.everyItem().overflows;
This gives you a list of the overflow state of all the stories in your document. So all we have to do is check them all to see if any is true, and if so find a way of alerting the user.

For this we need a for-loop. I write them in a slightly odd way to avoid the use of the < because it is a bit of a pain to have to substitute &lt; every time I need it. Hence:
for (j = 0; myOsetList.length > j; j++) { 
if (myOsetList) {
// j-th story is overset; tell the user and exit
selectIt(myDoc.stories.textFrames[-1]);
exit();
}
alert("No stories are overset");
If you've been following along adding these lines to the script and you try to run it now, it'll work if there are no overset stories, but if there is one, it'll have a hissy fit when it gets to the "selectIt" line. That's a call to a function that we haven't included yet.

The idea here is that having located an overset story, what we want to do is make it visible in the UI to the user. Well, you can't select a story because it is only a logical entity. It manifests itself in the UI as text in text frames. These can be selected but not the story itself.

Selecting the text of the story might be helpful, but more likely selecting the last text frame is what you want. That's why I passed: myDoc.stories[j].textFrames[-1] to the function we still have to write. That's a reference to the last text frame of the j-th story which at the point we make this call we know to be overset because its overflows property is true.

There are lots of ways you might go about trying to select that text frame to show it to the user, but InDesign makes this very easy -- even though it took me over two years to realize this.

So let's write the selectIt function. It takes one parameter (or argument -- depends on your background which term you use; they mean the same). In our main script, we're passing a reference to a text frame, but the function can have much wider scope. It can work with anything that is selectable.

We might want to use this same function to highlight some text and show it to the user or a group that has certain characteristics, or anything at all.

So, our formal definition of the function uses a surrogate name for the argument:
function selectIt(theObj) { 
// Selects object, turns to page and zooms in on it
app.select(theObj,SelectionOptions.replaceWith);
app.activeWindow.zoom = ZoomOptions.fitPage;
app.activeWindow.zoomPercentage = 200
}
As written, this assumes that the parameter passed "theObj" is indeed selectable. I suppose it might be worth checking for that and issuing an error message. But I've been happy enough in my use of this function to not bother.

So, the first statement selects the referenced object, replacing whatever selection there might previously have been -- this is actually the default behavior of the app.select() method describe on page 676 of the guide, but it does no harm to explicitly call for this behavior.

Now, the script takes advantage of something I didn't cotton on to for a long time. Once you've made a selection, whichever page it is on becomes the activePage of the activeWindow [even if it is not visible! this is like using the vertical scroll bar to view some other part of a document -- you might be looking at page 168, but if you have a selection on page 3, that's still the active page], so the script doesn't have to worry about where it is because InDesign already knows and will do all the hard work for me -- when I think about the stumbling attempts I made to do this myself, looking for the parent to determine the active page, I could kick myself.

All I have to do is zoom the activeWindow and InDesign turns to the page all by itself. So why the double zoom?

Because if the selected object is not visible in the window, the zoom to 200% will zoom in on the current center rather than the selected object.

There you have it. Piece this lot together and you have a script that will detect and select the last text frame of the first overset story it finds.

I'll leave it as an exercise to handle the table cells.

By the way, I don't think this script, as is, will work with InDesign CS. I believe for it, you have to check the overflows property of the last text frame of each story because of a bug that is fixed in CS2.

Comments:
Did you try "selectIt(myDoc.stories[0].textFrames[-1]);" too?
 
Hi
We are looking for someone who can create an Indesign CS4 Script for Windows in Javascript, using Indesign's script library. The script needs to
1. Place one Word Documents in Word 2007 from a series of Word documents in a nominated folder (C:\Documents and Settings\Administrator\Desktop\PDOCS) into the second page of an Indesign CS4 template (as a Word 2007 imported document)
2. Save the resulting document with a unique file name to a second nominated folder C:\Documents and Settings\Administrator\Desktop\INDOCS)
3. Search for the next Word Document and repeat the process until all the documents are completed in the relevant folder.
Do you know of anyone who might be able to complete this task quickly, and what they might charge?
Many thanks

Peter
 
Peter,

I doubt there's enough traffic here that you will get much response to your comment. I suggest you post it at the Adobe U2U forums, here: http://forums.adobe.com/community/indesign/indesign_scripting

Dave
 
Dave, there is an error in your JavaScript. If there are others like me that use your page as a guide then they'll be confused about the line

selectIt(myDoc.stories.textFrames[-1]);

which ought to be

selectIt(myDoc.stories[j].textFrames[-1]);

just as you claim later in the text
 
Post a Comment

<< Home

This page is powered by Blogger. Isn't yours?