is now part of CreativePro.com!

Add Undo to Your Script

15

Javascripts are great. They can do lots of things, and they can do so at a fairly fast speed, and they can do the same thing again and again without making any mistake… well, that’s the intention, at least. Sometimes you start up a script and you see it go wild, moving stuff around and deleting and copying — and you realize, -Wait! That’s not what I wanted!  Perhaps you fired up the wrong script, or at the wrong time, or perhaps the script does not do exactly what you were hoping.

Time to choose Undo… but unfortunately, most scripts don’t let you undo in one step. That is, if the script performed 100 actions, you’ll have to Undo 100 times! That’s bad. Even worse, it doesn-t help that InDesign’s Undo seems to have a mind of its own, and when you hit Undo, the view jumps to some random page, rather than the one you were hoping to see.

Fortunately, a tiny bit more scripting can help considerably! (Unfortunately, this only works when scripting CS4 and newer.)

The Original Script

Let’s start with a nice small Javascript that I wrote just the other day to expand US state abbreviations to their full form. For each of the items in the text array at the top, it searches for the abbreviation and replaces it with the full name:

var abbrevs = [ [ "Alabama", "AL" ], [ "Alaska", "AK" ],
[ "Arizona", "AZ" ], [ "Arkansas", "AR" ] ]; // Some more states left out for brevity
app.findGrepPreferences = app.changeGrepPreferences = null;
for (a in abbrevs)
{
app.findGrepPreferences.findWhat = "\\b"+abbrevs[a][1]+"\\b";
app.changeGrepPreferences.changeTo = abbrevs[a][0];
app.activeDocument.changeGrep();
}
app.findGrepPreferences = app.changeGrepPreferences = null;

For this trick, it’s not important that you understand everything that is going on in that script, but here’s a quick rundown anyway: Each element in the array at the top (the stuff in square brackets) contains two elements in turn, the full name and the USPS abbreviation. The script uses a for command to loop through each of elements in the “main” array (it does it (a in abbrevs) times), and replaces the second element of each (the abbreviation) with the first one (the full name). I use GREP for convenience: it’s case sensitive by default and finds just entire words with some added codes. After all, you definitely don’t want to have it replace each occurrence of “ar” in your text.

That’s all fine and dandy, and if you run the script you’ll see it works as expected. But if you change your mind, and try to Undo- The script performs up to 59 separate Find-and-Change operations (well, this one does only four, but the original does them all), and so you would need to Undo 59 times. You might think, “oh I can just write a script that does an undo 59 times,” but if an abbreviation wasn’t found, it won’t register as an Undo-able action, so you can’t count on it being 59 all the time. Sigh.

So here’s how to collapse the entire script into one single Undo, in two easy steps:

  1. Wrap the script in a single “function”.
  2. Add a code fragment at the top.

Sounds easy? It is! But please always make sure to keep a copy of the original in case something goes wrong while you edit it. (That’s just a good version-control practice for any scripting work.)

Step 1

A function in Javascript begins with the word “function”, followed by the name of the function, and optionally followed by a list of arguments. In this case, we don’t need to bother with arguments, so we’ll leave it blank. The function “body”, as it is known, should be inside matching curly braces at the very top and bottom:

function doReplace ()
{
var abbrevs = [ [ "Alabama", "AL" ], [ "Alaska", "AK" ],
[ "Arizona", "AZ" ], [ "Arkansas", "AR" ] ]; // Some more states left out for brevity
app.findGrepPreferences = app.changeGrepPreferences = null;
for (a in abbrevs)
{
app.findGrepPreferences.findWhat = "\\b"+abbrevs[a][1]+"\\b";
app.changeGrepPreferences.changeTo = abbrevs[a][0];
app.activeDocument.changeGrep();
}
app.findGrepPreferences = app.changeGrepPreferences = null;
}

Theoretically, you can add this function wrapper to every script, even ones that contain functions of themselves. Take care to use a unique name for the function. Javascript doesn’t have many “reserved” words (function is one; so are var, break, for, while, if, else, return, and switch; there are some more), and you cannot use one of these. You also cannot use digits at the start, and – most important! – the name should not appear anywhere else in the script, so abbrevs is out as well. On top of that, ExtendScript reserves lots of names for InDesign objects: Page, Font, Hyperlink, etc. Best is to use something personal with a combination of lowercase and uppercase, like myCoolFunction.

Step 2

Add these lines at the very top of the script:

if (parseFloat(app.version) < 6)
doReplace();
else
app.doScript(doReplace, ScriptLanguage.JAVASCRIPT, undefined, UndoModes.ENTIRE_SCRIPT, "Expand State Abbreviations");

Of course you should replace the highlighted names of the function with the name you used -note that it appears twice.

This is InDesign version-friendly code, because the very first line checks if it’s running under an earlier version than CS4 (which unfortunately has the version number “6”). CS3 and earlier versions do not support “Undo” in its “doScript” command. Of course you know what version your script will run on, but you may want to hand it over to someone who has an older version, so it’s nice to have this little check.

You also need to change the Undo name at the end of the bottom line (that’s the third highlighted text in the code above). This is the text that appears in the Edit menu; in this case, it will display “Undo Expand State Abbreviations”, and after have you undone the deed it will show as “Redo Expand State Abbreviations”.

Save the modified script in the usual place to make it appear in your Scripts panel, and try it out on a simple document. If all’s gone well, the script will run and you’ll see its usual behavior, but if you check the Edit menu, you will find you only have one step to undo: the entire script!

Dutchman Theunis de Jong is better known under his nickname "Jongware" and has been bothering people since 1966. Started as pressman's little helper in 1984, fresh from school. Really wanted to work in the graphics industry, so he had no problems starting at the very bottom :-) His computer talents were soon discovered, first by himself (being a math & science buff), then by his boss who quickly promoted him to the typesetter division. Worked with WordPerfect, PageMaker, and now InDesign -- but never with Quark. Well, hardly ever. Tends to dive in deep into technical stuff: PostScript, XML, XSLT, general programming in C++ (for DOS, Windows, and Mac OS X). Wrote programs in pure assembler for Z80, ARM4, 80x86, and 68000 processors. As it appeared, this seems the perfect background for right-brain activities such as Scripting and GREP, two InDesign features he fanatically uses (and occasionally abuses). Or was it left-brain? I always forget. I mean, he always forgets.
  • Dan Rodney says:

    When I’ve tried this I’ve had weird issues later with undos. For instance if I undo, later undos start undoing the wrong thing. I think it may have been something I had in my function.

    Is there anything we should avoid in our scripts (the function part) that might create problems for undoing?

  • mr. brown says:

    Very nice, instead of clicking thousand times “cmd”+”z” or start all over again. So if i understand correctly, the scripts will look like this?

    if (app.version < "6")
    doReplace();
    else
    app.doScript(doReplace, ScriptLanguage.JAVASCRIPT, undefined, UndoModes.ENTIRE_SCRIPT, "Expand State Abbreviations");

    function doReplace ()
    {
    var abbrevs = [ [ "Alabama", "AL" ], [ "Alaska", "AK" ],
    [ "Arizona", "AZ" ], [ "Arkansas", "AR" ]
    ]; // Some more states left out for brevity
    app.findGrepPreferences = app.changeGrepPreferences = null;
    for (a in abbrevs)
    {
    app.findGrepPreferences.findWhat = "\\b"+abbrevs[a][1]+"\\b";
    app.changeGrepPreferences.changeTo = abbrevs[a][0];
    app.activeDocument.changeGrep();
    }
    app.findGrepPreferences = app.changeGrepPreferences = null;
    }

  • Jongware says:

    Indeed it is, mr. Brown. Good to see you were paying attention ;)

  • Jongware says:

    Dan: there are circumstances in which InDesign might have problems with Undo’ing; see the Adobe forum thread doScript (UndoModes) breaks InDesign’s Undo (CS4) (or Marc Autrets’ concise summary).
    After the Long Technical Discussion it seems to me this only happens when you try advanced scripting trickery, and sticking to the regular “Undo mode” should usually work.

  • Marc says:

    Hi Jongware,

    Thanks for the mention.

    Just a little (although important) point.

    Your test:

    if (app.version < "6")

    is semantically wrong and technically dangerous. Since app.version is a String, this forces the interpreter to apply a String comparison, operands are not coerced to numbers.

    So, when InDesign CS8 will be released, app.version < "6" will return TRUE?which is probably not what you want. Indeed, "10" < "6" is true in JavaScript.

    Hence, you should definitely prefer the following syntax:

    if (app.version < 6)

    Or even better:

    if( 6 > parseFloat(app.version) )

    @+
    Marc

  • Jongware says:

    Ah, and I thought I had discovered a handy shortcut! You are correct, although it will take a couple of years to surface.

    I bet that’s how programmers used to think of the YK2 problem.

    (22-Dec: Adjusted in the article.)

  • Eugene says:

    Up to now, my undo for scripts was “File>Save” then load the file up again if the script wasn’t successful :)

  • albastru22 says:

    Well… loading the file again wouldn’t always work… Like if a script would change application settings…
    Jongware – excellent posts on javascript ! I really hope to see more of this on indesignsecrets.com, it is a field where adobe failed to bring help for beginners, real help, not only those pdfs.

  • Eugene says:

    An application setting wouldn’t affect the document and can be easily changed back… when you discover what setting it was it changed. I’ve often run scripts and realise days later that the ruler was set to a different measurement system.

    Let’s face it, the only reason you want and undo system is to undo the entire script the made physical changes to your entire document that you need undone.

  • Trevor says:

    I thought it worthwhile mentioning the adding.
    #target “InDesign”
    #targetengine “session” // or any other name in quotes
    app.scriptPreferences.enableRedraw =0;
    Can speed up some scripts literately hundreds of times.

  • Kenny says:

    Hello experts!
    I need some script for making PDF and EPS for same file (page) at a time(with same settings).
    (for ex: opening a new document indesign cs5. after completing the page work in indesign i have to export to EPS & PDF. now i am making those eps & pdf manually taking too much of time)
    my kind of work is to make pdf and eps of indesign file nearly 50 to 100pages.
    script will be very helpful for me to run timing consumption.
    Thanks in advance

    Thanks,
    Kenny

  • Kelly Bellis says:

    Hey Jongware – very cool!

    Thanks for the script *and* for the article.

    I just added this into my TextPad Clip Library :)

  • Greetings, I do believe your blog could possibly be having internet browser compatibility issues.
    When I take a look at your web site inn Safari, it looks fine but when opening iin Internet Explorer, it has some overlapping issues.
    I just wanted to give you a quick heads up!
    Other than that, fantastic blog!

  • Jose Antonio Pulgarin Alvarez says:

    Hi all!
    I must to revive this post cause I can’t make work this undo way.
    I have a indesign CC 2019 script with almost 100 actions, and an “one undo to rule them all” is a great gift. I have written it right, indeed I have copied-pasted from is Jongware example, and from another one in Adobe forums. The script works like before to add this piece of code, but app undo only shows and undoes individual steps.
    Any help to do it work?
    Thanks in advance, and sorry for my English.

  • Jose Antonio Pulgarin Alvarez says:

    Me again.
    Just tried in CS5, same result that in CC 2019.

  • >