Script Objects: Deep Dive

Anyone looking to reduce code repetition in their forms will be familiar with script objects.  Script objects are a way to group functionality in a single location and share it from multiple places.  Most users will localize some logic in a series of JavaScript functions.  Advanced JavaScript programmers will try to do more.  And at that point they will notice some quirks around script object usage.  In this blog entry I’ll give a little background as to how script objects are implemented internally and also how advanced programmers can use them and still flex their programming skills.  We’re pretty much in the deep end here.  If you’re a novice/intermediate script writer, you’re not the target audience for this information :-)

Managing Script Objects

An XFA script object is very much like a JavaScript "class" (where a class is represented as a function).  When you create a JavaScript class object you might code something like this:

function Rectangle() { 
    this.width = "0in";
    this.height = "0in";
    this.getInches = function(measurement) { … }

The user of this class can then write code such as:

var rect = new Rectangle();
rect.width = "8cm";
rect.height = "4cm";
var area= rect.getInches(rect.width) * rect.getInches(rect.height);

The script object equivalent would look like:

form1.#variables[0].Rectangle – (JavaScript, client)

var width = "0in";
var height = "0in";
function getInches(measurement) { … }

and your calculation using the script object would look like:

form1.NumericField1::calculate – (JavaScript, client)
Rectangle.width = "8cm";
Rectangle.height = "4cm";
var area= rect.getInches(rect.width) * rect.getInches(rect.height);

Notice two things that the form author didn’t have to worry about: creating the script object and exposing properties for external access (prefixing members with "this.".  The internal machinery to manage the script object makes authoring easy.  However, this machinery can get in the way of advanced users.  But hopefully by the end of this entry the advanced users will have enough new data to accomplish what they want as well.

Creating Script Objects

Each script object gets created the first time it is referenced. If there are no form scripts that use the script object, it will never get created.  Note also that if your script object is housed inside a repeating subform, we will create one instance of that script object for each instance of the repeating subform.

There are a couple of things you want to be aware of related to the creation of script objects:

1. When a script object is created, Reader will execute the body of code in the object.   This is where any variable initialization happens.  In our example, this is where width and height get initialized.

2. If there are problems in your script object code and if executing the code results in a JavaScript exception, the script object will not initialize correctly. Depending on the error, it might be partially initialized or it might fail completely. Unfortunately, Reader is inconsistent in reporting errors during initialization.  You might not get a message in your console and the symptom will be that any script that references the script object will get an error such as:

Rectangle.getInches() is not a function

The reason for this error is that because of the initialization failure the script object didn’t expose
getInches() as a function.

Exposing Properties

It is a bit much to expect novice (or intermediate) form authors to understand the need to define object properties in order to use them externally.  For the 95% use case, the form author just wants to define some functions and reference them from other script.  To make this easy, the XFA processor examines the JavaScript looking for function and variable declarations.  For each declaration, it exposes the declared object as a property.  The actual internal mechanism for exposing properties varies between client and server.  On the server, the script code gets modified to add declarations at the end:

form1.#variables[0].Rectangle – (JavaScript, client)
var width = "0in";
var height = 0in";
function getInches() { … }; 


On the client (Reader/Acrobat), the properties are exposed by registering them with the internal script object.

Script Object Behaviours

References to "this"

As mentioned in previous posts, we use different JavaScript engines on the client and on the server.  If you are pushing on the boundaries of script object functionality, you will notice some differences in the two implementations. 

  • On the client (Reader/Acrobat), references to "this" inside a script object refer to the subform hosting the script object.
  • On the server, "this" evaluates to the script object itself

Dynamic Properties

On the client, a script object can be dynamically extended with new properties (even with strict scoping on).  On the server a script object cannot be dynamically extended with new properties


Don’t give script object functions the same name as the script object.  eg. don’t create a function named "foo" inside a script object named "foo".  The name lookup mechanism will fail to resolve this correctly.  You’ll get an error such as: is not a function

Using the new operator

If you want to define a JavaScript ‘class’ in your script object, you need a workaround to manage it correctly.

eg. suppose your script object defines a rectangle:

form1.#variables[0].R – (JavaScript, client)
function Rectangle(width, height) {
  this.w = width;
  this.h = height;

In your script you’d like to create an instance of this rectangle.  Ideally you would simply code:

var rect = new R.Rectangle(4,5);

Unfortunately, the new operator does not work in this context.  The workaround is to include a helper function in your script object:

form1.#variables[0].R – (JavaScript, client)
function Rectangle(widt
h, height) {
  this.w = width;
  this.h = height;
function newRectangle(width, height) {
    return new Rectangle(width, height);

In order to create a Rectangle object your code can call:

var rect = newRectangle(4,5);

Exposing Variables as Properties

If you have variables that you want to use both within your script object *and* expose outside your script object, you need to be careful how you reference them.  Consider this example:

form1.#variables[0].R – (JavaScript, client) 
var width=0;
var height=0; 
function area() {
    return width * height;

As described above, on the server, this gets modified to:

form1.#variables[0].R – (JavaScript, client) 
var width=0;
var height=0; 
function area() {
    return width * height;
this.width = width
this.height = height;
this.area = area;

While this code addition nicely exposes the properties to the rest of the form, in doing so we create a copy of the width and height variables.  We now have both variables and properties named width and height.  When the width and height are referenced outside the script object, it is the property versions that are used.   However, when referenced inside the script object, it is the copies declared as variables that are referenced.  For the example above, on the server an external script could modify R.width and R.height, but the area() function will always return zero.  On the client because we expose the properties using a different mechanism, the external and internal references both access the property versions of the object.  In order to make your script work reliably on both client and server, make sure that references within the script object always predicate with "this":

form1.#variables[0].R – (JavaScript, client) 
var width=0;
var height=0; 
function area() {
    return this.width * this.height;

Variables in Script Objects

If you want to use variables in script objects that are preserved during your forms session, you need to be aware of variable scope issues.  These are described in previous blog entries: Scope of JavaScript Objects and Form Compatibility.

Design Pattern 1

Not happy with the way the XFA processor is managing your script object?  There is a workaround that allows you to regain some control.  Personally, I’m most interested in keeping my variables private.  Those of us who have written code in C++ and Java have always been encouraged to follow this pattern.  You provide the outside world access to your private members through accessor functions/methods.

The code in Reader that’s parsing your script looking for declarations can be fooled.  When the parser encounters a brace, it stops looking for declarations to expose.  If you enclose parts of your script with braces, you will have more control over how your class is handled. This is best explained by example:

form1.#variables[0].Rectangle – (JavaScript, client) 
{   // Opening brace causes parser in Reader to ignore contents

    // Declare these variables and keep them private
    // calcs (in Reader) will not be able to access Rectangle.width
    var width = 0;
    var height = 0; 
function setW(W) { width = W; }
function setH(H) { height = H; }
function getW()  { return width;  }
function getH()  { return height; }
function area()  { return width * height; }

Code referencing this script object can look like:

this.rawValue = Rectangle.area();

Note that this code works on the client and server — however on the server, the XFA processor discovers the variables without performing a parse — we use a function on the script engine itself.  The server script engine is not fooled by our trickery.  The impact is that on the server, code will continue to be able to access Rectangle.width and Rectangle.height.  As long as your form is tested on both client and server you might be willing to live with this behaviour.

Design Pattern 2

A second technique works a bit harder to hide member variables:

form1.#variables[0].Rectangle – (JavaScript, client) 
function R() {

    // Declare these variables and keep them private 
    var width = 0;
    var height = 0;

    // Declare these functions and make them public
    this.area = function() {return width * height;}

    // Provide accessors to private variables
    this.setW = function(W) { width = W; }
    this.setH = function(H) { height = H; }
    this.getW = function()  { return width;  }
    this.getH = function()  { return height; }
{   // add braces to hide this declaration in Reader

    var Rect = new R();
function setW(W) { Rect.setW(W); }
function setH(H) { Rect.setH(H); }
function getW(W) { return Rect.setW(W); }
function getH(H) { return Rect.setH(H); }
function area()  { return Rect.area(); }

Notice that the variable Rect will end up exposed on the server, but since it has kept its private members hidden, other scripts will not be able to modify the object (although on the server the entire variable could be replaced).  But this is a good design pattern for a coup
le of reasons:

  1. On the server when the XFA processor adds the code: this.Rect=Rect; we do not end up with a copy of the variable, because assigning to an object creates a reference, not a copy.
  2. Limiting access to member variables via function calls means that we will not fall into the classic JavaScript trap where we inadvertently assign a new property to an object when we misspell the member name.