This tutorial shows you how to work with the JavaScript features in Acrobat X. See what the all-new Acrobat DC can do for you.
Download a free trial of the new Acrobat.
An Action in Acrobat X Pro is a set of pre-defined commands that are applied sequentially to a list of PDF files. In previous versions of Acrobat this was called Batch Processing, and there is a treasure trove of information on creating and using Actions in articles and videos on this site. There is also an Actions Exchange, offering all kinds of fancy Actions -- created by users – that can be downloaded.
The "commands" that make up an Action are, for the most part, the standard operations on the regular menu items and tools in Acrobat, such as deleting a page or applying security. Anyone who is already familiar with Actions will know these commands are fairly static. They are set up to perform a specific activity, such as deleting page five of a PDF. There is no way to set up the page-delete command to be more flexible, for example, to delete the second-to-last page, for which the specific page number will change with the document length.
To create an Action where the operational parameters are flexible requires scripting, and fortunately, the Actions Command Palette includes a command that runs JavaScript (Figure 1). In fact, most of those fancy Actions on the Actions Exchange are implemented with the "Execute JavaScript" command. It is worth the time to examine the scripts in these actions. It is simply not possible to get the kind of advance functionality used in these Actions any other way. This article will explore the rules and techniques for writing such scripts.
Figure 1 – The Execute JavaScript command provides the only way to apply automatic, non-interactive flexibility to an Action.
There are some big advantages to running a script in an Action, as well as a number of serious limitations. It's important to understand both sides in order to write efficient, error-free scripts. In addition, as we'll see, to get the most out of an Action Script, a little creativity is needed to work around some of the limitations.
Basic action-script operation:
Most of the scripting used in an Action is very straightforward, and is written in exactly the same way as the code placed in a folder-level trusted function. For example, here is a single line of code that places the file name along the bottom edge of all pages in the PDF using the watermark function.
event.target.addWatermarkFromText({ cText: event.target.documentFileName, nFontSize: 10, nVertAlign: app.constants.align.bottom, nVertValue: 18 });
The script is simple, but it makes a custom change to the PDF that could not be done with any of the other Action Commands. If the document has an existing watermark, the new footer text is added to that watermark, so the code does not destroy anything already on the PDF. Notice that event.target is used as the document object. In this code, the "this" keyword could also have been used, but using the official, and explicit reference to the document object is a much better practice
Saving to a custom file name:
The Actions Dialog provides several options for saving the files operated on, including options for renaming the files with prefixes and postfixes. However, these additions to the file name are static. A script is the only way to provide a flexible, custom-file-name option. For example, here is a short script that appends the current date to the file name and saves it into a subfolder of the original file.
// Create the Destination save path from the existing file path var aDstPath = event.target.path.split("/"); // Remove File Name from end aDstPath.pop(); // Add subfolder aDstPath.push("result"); // Build File Name addition from the current date var cExt = util.printd("_ddmmmyyyy",new Date()) + ".pdf"; //Add the extension to the end of the file name var cDstName = event.target.documentFileName.replace(/\.pdf$/,cExt); // Build the full destination Path aDstPath.push(cDstName); var cDstPath = aDstPath.join("/"); // Save the file event.target.saveAs(cDstPath);
It seems like there is a lot to this script, but it is really just a simple set of text manipulations to build the destination file name and path. This script must be the last step in the Action, and the SaveAs portion of the Actions Dialog must be set to "Don't Save", as shown in Figure 1.
Building a report file
This technique is very useful for analyzing documents. It's used by the Create Comment Summary Action in the Actions Exchange. The code in that example is very complex. A simplified version will be presented here. For this example, a script will be created that reports the number of pages for each document run through the Action.
It was stated earlier that there is no way for the script to know whether a document is the first or last in the process, so a method is needed that does not depend on detecting the beginning or end of the Action process. To deal with this issue, the script will use the report document itself. If no report document is open, then one will be opened. If it is open, then the script will add information to it. In this example, it is assumed there is a generic report document, available in a known location. It is also assumed that the report document contains a list field where the collected data will be placed. Here's the script:
// Test for existence of Report doc by accessing a property try{global.oReportDoc.path} catch(e) { // Perform startup action global.oReportDoc = app.openDoc("/c/reports/PageNumReports.pdf"); //Clear list global.oReportDoc.getField("PageNumList").clearItems(); } // Collect Data and stuff into Report Doc var cReportText = event.target.documentFileName + ": " + event.target.numPages + " Pages"; global.oReportDoc.getField("PageNumList").insertItemAt(cReportText);
The document object for the report doc is stored in the global object. The first line detects the existence of the doc object by testing a property of that object. If the document was never opened, or if the user closed the report doc from a previous run, then the JavaScript engine will throw an exception and the catch block will open and initialize the report document. This is just one method. The report doc could have been detected in a different way. For example, the open report doc is also listed in the app.activeDocs property.
The location of the report document is completely arbitrary; it could be placed anywhere that is convenient. In the Create Comment Summary Action, the report document is created at runtime using the app.newDoc() function. It also could have been done using the Report object. The important bit is to have an open PDF document to act as a repository for the collected data. It's also a good idea to include fields on the report doc for displaying the time and date of the Action run, and maybe even the user name.
Once the report document is open, data will be added to it every time the Action is run. To create a clean report document, this file should be saved to a new location and closed. The save action can be added to the startup code in the catch block, for example saving to the folder of the first document. This is the best place to put an automatic save. There is no easy way to know when the last document is processed, so the save, if it is added to the code, has to happen when the document is first opened.
Getting user input at the beginning of the process
For some actions it is necessary to ask the user for input parameters in order to run the Action. If the same parameters will be used for all documents processed, then this should happen only once, at the beginning of the process. The challenge, of course, is detecting the beginning of the process.
The answer to this challenge is provided by the previous example that uses a report document. The report document can be used as a kind of marker to determine the state of the process. If the report document is not open, then the Action has just started and the script displays a popup or custom dialog for collecting data. If the report document is already open, then the script proceeds with the business logic of the Action. This technique works nicely, but it is a bit awkward because the report is still open when the Action is complete. If the user wants to run the Action with new parameters, they must close the report document. Unfortunately, there aren't any other less-awkward solutions.
Here's a short demo script that shortens the number of pages in a PDF to the number of pages specified by the user.
// Test for existence of Report doc by accessing a property var bDoStartup = false; try{global.oReportDoc.path} catch(e){ // Perform startup action bDoStartup = true; } // Also test for page length parameter if(bDoStartup || !global.nPgLen) { global.oReportDoc = app.openDoc("/c/reports/DocTruncReport.pdf"); // Ask user for input var bRedo = true; while(event.rc && bRedo){ global.nPgLen = app.response("Enter Max Length of PDF"); bRedo = isNaN(global.nPgLen) || (global.nPgLen == 0); if(bRedo) event.rc = (4==app.alert("Do you want to continue? ",1,2)); } } // Perform page truncation if necessary for current doc if(event.rc && (event.target.numPages > global.nPgLen)) event.target.deletePages(global.nPgLen, event.target.numPages-1);
The business logic for this script is only two lines of code, i.e., the last two lines in the script. Everything else is preparation. The script starts off by testing for both the existence of the report doc and the global.nPgLen parameter; both are necessary. If either does not exist, then the report document is opened and the user is asked to enter the page length used to truncate the PDFs. The code setup guarantees the user will only be asked to enter this information at the beginning of the Action execution. This is where things get interesting.
The response box is displayed inside a while loop. This is a simple technique for ensuring the user enters a valid value, in this case, a number. The loop will continue to display the response box until the user either enters a valid number or cancels the Action altogether. This second bit is handled by an alert box that is only displayed if the data entered by the user is invalid. The user is asked if they want to continue the process. If they answer "yes," then the response box is displayed so they can enter another number. If they answer "no", then event.rc is set to false, which aborts the Action. Notice that event.rc is used to qualify both the "redo" loop and the main business logic for the Action.
All the techniques used here are also used in the Extract Commented Page Action, available in the Actions Exchange. The code in that Action is much more complex than what is shown here, but the technique is the same. In a working script, the popup messages should be more verbose and information about the process should be written to the report document.
Products covered: |
Acrobat X |
Related topics: |
JavaScript |
Top Searches: |
Edit PDF create PDF Action Wizard |
Try Acrobat DC
Get started >
Learn how to
edit PDF.
Post, discuss and be part of the Acrobat community.
Join now >
0 comments
Comments for this tutorial are now closed.
Comments for this tutorial are now closed.