Client/Server Scripting Differences

One of the brand promises of XFA is that you can design your form once and re-use your form design in multiple contexts.  Most notably: design once and re-use for both data capture and for printing.  Re-use means that the form looks the same whether rendered as interactive PDF or PDF/A or Postscript or PCL or ZPL …  It also means that your calculations and script logic work the same in all environments.

We process XFA documents in multiple environments — in Acrobat/Reader and in LiveCycle server — via Forms and Output.  We refer to Acrobat/Reader as "client" and LiveCycle as "server".  Our challenge is to make sure that the forms get consistent treatment in both client and server.

The good news is that you could design hundreds of forms and never run into any differences between the XFA implementations on the client and server.  But if you are making extensive use of JavaScript, you will want to be aware of some boundaries.  Many users begin designing forms for only one environment — usually for interactive PDF first.  I have witnessed a couple of occasions where users with extensive script frameworks were surprised when their forms did not work on the server right away.  There are some differences.  In this post I will list those that I am aware of.

Different Engines

I have previously pointed out that we use different JavaScript engines on the client and server.  The JavaScript engine inside Adobe Reader is the Mozilla engine.  The engine on the server is an Adobe-implemented engine (Extendscript).  Extendscript is also used in all the Adobe Creative Suite applications.  The good news is that JavaScript is a standard.  Being based on a standard allows differing JavaScript engines to behave compatibly.  The bad news is that standards are not always implemented consistently.  Just ask an experienced web author who writes JavaScript for multiple browsers.

Handling Differences

Before describing specific differences, it is important to note that you can write script conditionally to handle the different environments.  The xfa.host.name property allows you to write code such as:

if (xfa.host.name === "Acrobat") {
    // do client stuff
} else if (xfa.host.name === "XFAPresentationAgent") {
    // do server stuff
}

Different Exceptions

When the XFA runtime encounters an error, we generate a JavaScript exception.  The exception objects have different properties on the client and server.  If all you ever do with an exception is call the toString() method, you don’t need to worry about the differences.  If you want to examine more details of the exception, you need to know about the differences.  Read more here.

Strict Scoping

Lots has been written about strict scoping: here and here.  The main take away is that while the client has support for non-strict scoping, the server has always been strict.  For forms to be compatible, choose strict scoping.  (Choose strict scoping even if you’re not concerned about server compatibility).

Script Objects

I have noticed recently that on the client, script objects inherit properties of their parent subform.  e.g. the expression "this.border" inside a script object returns the border property of the parent subform.   On the server, the script object does not inherit:  "this.border" is undefined.  To ensure compatibility, make sure your script object doesn’t make use of the inherited properties.  If you need to access parent subform properties, reference the subform explicitly (by name).

Global Variables

JavaScript variables declared in calculations have different scope in client and server. Variables used in scripts on the server are global to the form.  Variables used in scripts on the client stay local to the script.  For example, suppose you have two fields F1 and F2 with these calculations:

form1.S1.F1::calculate – (JavaScript, client)
var foo = "field 1 value";
this.rawValue = foo;

and

form1.S1.F2::calculate – (JavaScript, client)
this.rawValue = foo;

On the server, F2 will evaluate to "field 1 value".  While on the client it will return a JavaScript error.

To ensure compatibility, make sure your variables are always declared.  Or better yet, limit the number of global variables you declare.  Put them inside functions.  Regardless, the best practise is to make your code strict by running jslint.

Logging

On the client we frequently use console.println() to issue debugging trace statements.  However console is an Acrobat object that appears only in the client context.  On the server use
xfa.log.message(); and your debug trace will appear in the server log file.

References to "$"

If you write script in FormCalc you will know that the "$" symbol refers to the current object. It is the equivalent of "this" in JavaScript.  What you may not know is that "$" works just fine in client side JavaScript.  However, it does not work on the server side.  Extendscript reserves the use of "$" as a global object to get and set system information.  e.g. $.stack will return a string with the current call stack.  For compatibility reasons, avoid using "$" in JavaScript.  Do not use it as a shortcut to "this" and do not use it as a variable name.

Script to Data

There are client/server differences in how you access data via script.
The short explanation:

If your root dataNode is named "form1" then on the client you can reference "xfa.datasets.data.form1".  On the server, "form1" does not appear as a child of "xfa.datasets.data".  Instead, you get to your root dataNode using $record (or xfa.record).  Using $record works reliably on both client and server.

The detailed explanation (definitely in the deep end):

There are settings in configuration (xfa.config) that impact how a form is processed.  Some config settings will be different on the server than on the client.  One such setting is xfa.config.present.common.data.adjustData.

Without getting into too much detail, the purpose of turning this setting on is to make sure that form data gets adjusted to match the form template so that we can reliably round-trip our forms — i.e. make sure a form will bind to data the same way after save/close/re-
open.  Having adjustData turned on requires more expensive processing.  On the server we usually set adjustData=false — since round-tripping isn’t a concern.  The impact on script is that when adjustData is false, we disconnect our data records from the datasets.data node.  Since they are disconnected, the expression xfa.datasets.data.form1 (or $data.form1) will not work.  But $record will reliably return the form1 node.