Acrobat User Community

The global object in Acrobat JavaScript

By Thom ParkerDecember 12, 2011

Global variables are about sharing data between different contexts within a software application. This can be quite simple, but it can also be quite complex. It depends on the data and what is meant by global. A variable can be global within the system, the application, the document or a smaller context. There are, in fact, many different techniques for storing data in a global context. In this article, we’ll explore the official global object in the Acrobat JavaScript model. But keep in mind this is just one methodology for sharing data.

The global object

The Acrobat JavaScript model has a global object for holding data that is global to the entire model. Below is a simple example of how it is used. There are two lines of code, each placed in a different location on the same PDF file in order to save and then restore the last page viewed by the user.

// Step 1: Will Close Script, Save the current page number 
global.savedPageNum = this.pageNum;

// Step 2:  Document Level Script, Restore the saved page number
this.pageNum = global.savedPageNum;

The WillClose script runs immediately before the document closes, so this is a perfect place to save the number of the last page viewed. The page number is stored by simply adding it to the global object as a property named savedPageNum. The global object can store all types of data-- numbers, strings, arrays and objects, as well as the built-in types such as dates. The document level script runs when Acrobat opens the document, restoring the last page viewed.

Persistence (simple data)

The data in the code above is stored in the global object as long as Acrobat is running, but disappears when Acrobat is closed. This works well for many applications, but in the sample above it is necessary for our document to open to the last page viewed even after Acrobat is closed and re-started. Fortunately, the global object has a very special feature for just this situation. Here is the same code as above with the addition of a line that makes the data persistent. 

// Step 1: Will Close Script, Save the current page number 
global.persistPageNum = this.pageNum;
global.setPersistent("persistPageNum", true);

// Step 2:  Document Level Script, Restore the saved page number
this.pageNum = global.persistPageNum;

The first argument to the global.setPersistent()  function is the name of the data property that will be saved. The second argument indicates whether persistence is turned on or off for that property. This function tells Acrobat to save the data in an external file when Acrobat is closed. The file is then loaded when Acrobat is re-started, which restores the value of the persistent variable. It is important to note that the external data file is stored in the local user area. Another user on the same system will not see the same data.  

Persistence (complex data)

The global object does not reliably persist data stored in objects. In fact, all of the built-in objects (such as the document object) cannot be persisted at all. If it is important to persist complex data, then the best strategy is to convert that data into a string, and then persist the string.

For example, the following three scripts together implement an auto-populate feature for a form. The auto- populate data is kept simple for the demo code. It is composed of a company name and an e-mail address in an object. The fields used in the example consist of a drop-down list of the company names, a text field for the e-mail address, and a button for adding or modifying a company entry (Figure 1). The dropdown and the text fields are editable to support the add/modify feature. 


Figure 1 – Only the e-mail field is auto-populated in this simplified example.

The customer data exists in two forms-- in a string and in an object. It is in string form for persistence and in object form for the working code. Both versions are members of the global object so that they are available to all scripts.

The first section of code below is a document level script that initializes the process. It acquires the saved string version of the data, converts it into an object, and initializes a drop-down list on the form with the company names so the user can make a selection.

// Document level initialization script
try{
  if(typeof(global.cCustomerData) != "undefined")
  {  // Data Exists, Convert saved string to an object
     global.oCustomerData = eval(String(global.cCustomerData));
  
     // extract names from the company data 
     var aNames = [];
     for(var cName in global.oCustomerData)
       aNames.push(cName);

     // Set up the drop-down field
     var oFld = this.getField("CustNameList");
     // Get the current value if any
     var cPrevSel = oFld.value;
     // Set new list entries
     oFld.setItems(aDepts);
     // Restore previous selection
     oFld.value = cPrevSel
  }
  else
  {// Create blank data set
     global.oCustomerData = {};    
  }
}catch(e){
  // Access to the global data was denied
  app.alert("Customer data is not owned by this document");
}

The code below is used on the Add/Modify button. It uses data manually entered into the same fields that are auto-populated to update both the working data object and the string for persisting the data. For this to work, both the drop-down list and the edit fields must be editable.

// Get the entered field data
var cCustName = this.getField("CustNameList").value;
if(cCustName != "")
{// Only operate on non-blank entries
  // Test for existing Entry
  var bNewName = (global.oCustomerData[cCustName] == null);

  // Add it to the working data object
  global.oCustomerData[cCustName] = this.getField("CustEmail").value;
  // Convert modified data object into a string
  // and save it in the global object.
  global.cCustomerData = global.oCustomerData.toSource();
  global.setPersistent("cCustomerData",true);

  // Insert name into drop-down list if it is new
  if(bNewName)
    this.getField("CustNameList").insertItemAt({cName:cCustName, nIdx:-1});
}

Lastly, the main event, the script below is for auto-populating the fields from a selection on the drop-down list. This script is placed in the Will Commit variation of the Keystroke event.  For the code to work properly, the drop-down must be set to “Commit selected value immediately.” 

if(event.willCommit)
{
   var cEntry = global. oCustomerData[event.value];
   if(cEntry)
   {// Got a Data match on an existing entry
      this.getField("CustEmail ").value = cEntry;
   }  // If no data match, do nothing 
}

It is important that the code ignore names entered by typing into the drop-down list, so there are two qualifying “if” statements. The first one makes sure the code is not run when the user is typing, and the second one only runs the code if an existing entry is selected. 

One of the great things about this code is that it works for both Adobe Reader and Acrobat. This means you can easily create auto-populate forms for everyone in your office, and even for customers, without having to worry about which type or version of Acrobat they own; however, there is one serious limitation.

Security changes to the global object

The global object it is not quite as global now as it used to be. In Acrobat/Reader version 10.1.1, Adobe made a semi-major change to the underlying structure of the global object for security reasons. In previous versions, the persistent data was saved in a plain text file that could be accessed by other applications on the system. This setup was convenient because it allowed the user to inspect and modify the persistent globals in a simple text editor. But in the latest version of Acrobat, this file was replaced by a proprietary caching mechanism that is much more difficult to hack than the old text file. 

This particular change (because there have been a few) however, does not affect scripting for ordinary users. The really big change for scripting happened in version 8. This is about the time when Adobe started to get paranoid over PDF security (and for good reason). The global object was of major concern because any script in any context could read it. If a user had sensitive data stored in the global object, then a malicious script in a random PDF could read it. To close this potential security hole, Adobe changed the global object so that the stored data was document specific, meaning the data could only be read by scripts in the same document where it was stored in the first place, or by a script in a privileged context. While improving security, this change means the global object cannot be used to share data directly between scripts in different PDFs.

The key word here is directly. If you try the auto-populate example above using the same code on two different documents, you’ll very quickly experience this multiple document limitation for yourself. It is unfortunate, since it is highly likely that in one office there will be many different forms that use the same customer data, and so require the exact same auto-populate data. The solution is to access the global data indirectly through a trusted function.

Globals and trusted functions 

A trusted function provides a privileged context, which means it can be used to provide access to everything in the global object, regardless of which document script is calling the function. A trusted function is needed to use the auto-populate functionality on multiple forms. The reason the trusted function is able to get around the security limitation is that it is installed on the user’s system in a JavaScript file, called a Folder Level script, typically used for automation scripting.

The following code is a folder level script containing three trusted functions. Each trusted function replaces one of the three scripts used in the previous auto-populate example.

var InitializeCustomerList = app.trustedFunction(function(oDoc)
{
  app.beginPriv();
  // Document level initialization script
  if(typeof(global.cCustomerData) != "undefined")
  {  // Data Exists, Convert saved string to an object
     global.oCustomerData = eval(String(global.cCustomerData));
  
     // extract names from the company data 
     var aNames = [];
     for(var cName in global.oCustomerData)
       aNames.push(cName);

     // Set up the drop-down field
     var oFld = oDoc.getField("CustNameList");
     // Get the current value if any
     var cPrevSel = oFld.value;
     // Set new list entries
     oFld.setItems(aDepts);
     // Restore previous selection
     oFld.value = cPrevSel
  }
  else
  {// Create blank data set
     global.oCustomerData = {};    
  }
  app.endPriv();
});


var AddModifyCustomerList = app.trustedFunction(function(oDoc)
{
  app.beginPriv();
  // Get the entered field data
  var cCustName = oDoc.getField("CustNameList").value;

  // Test for existing Entry
  var bNewName = (global.oCustomerData[cCustName] == null);

  // Add it to the working data object
  global.oCustomerData[cCustName] = oDoc.getField("CustEmail").value;
  // Convert modified data object into a string
  // and save it in the global object.
  global.cCustomerData = global.oCustomerData.toSource();
  global.setPersistent("cCustomerData",true);

  app.endPriv();
  // Insert name into drop-down list if it is new
  if(bNewName)
    oDoc.getField("CustNameList").insertItemAt({cName:cCustName, nIdx:-1});
});

var PrePosCustomerFields = app.trustedFunction(function(oDoc)
{
  app.beginPriv();
  if(event.willCommit)
  {
    var cEntry = global. oCustomerData[event.value];
    if(cEntry)
    {// Got existing Data
       oDoc.getField("CustEmail ").value = cEntry;
    }  // Otherwise just ignore it 
  }
  app.endPriv();
});

Notice the code is almost exactly the same as the previous three scripts. The only difference is the document pointer “this,” which has been replaced with the “oDoc” function argument. 

When not to use the global object

The global object is a very quick-and-easy way to store global and persistent data. It works quite well for folder level scripts, but it is not always the best choice. As noted in the section on security changes, when the code is entirely within a document, it cannot acquire data saved by another document. This type of operation requires a trusted folder level script, so in a way the global object is more useful when used for automation scripting than it is for document scripting.

There are also many other limitations and issues. First, the global object can be a little too global. It is a system- level storage mechanism (i.e., it persists data on the system for the current user only). If data needs to be persisted for the document or for all users, then another data persistence mechanism will need to be used, such as storing data on the document in a hidden field or in the document metadata. If persistent data needs to be modified outside of Acrobat or made visible to several users, then it is probably better to use standard data files shared across a local network or a server-based solution.

If data only needs to be stored temporarily for sharing between a few fields or a document script, then it is better to use a document level variable. If state information for individual fields needs to be stored, then it might be better to store the data on the field object itself. 

The global object is best used for sharing data in privileged automation scripts, to persist simple data for a single document, or to share temporary data within a document.   

The scripts described in this article, and more, can be found in the GlobVars_Sample.pdf file.