Sample: Convert to Strict Scoping

In the past couple of months I’ve had the opportunity to help a couple of customers through migrating a form design so that it was not dependent on non-strict scoping.

First, to review what this means, you could read this blog post describing the problem.

There’s no single technique to convert a form script so that it conforms to strict scoping. But there are a couple of common patterns.  I’ll disclose these by way of an example.

I’ve designed a sample form that makes use of “expando properties” to add multi-step undo/redo functionality to individual form fields.  In this case, the form adds an expando script object to a text field.  For review purposes, the reason this is problematic is that allowing users to modify the JavaScript representations of XFA objects means that the script engine must preserve these JavaScript objects. On large forms, this means we cannot release JavaScript objects to garbage collection and memory usage climbs.

var This = this;

This.ctl = new function() {
    this.history = [This.rawValue],
    this.current = 0,
    this.startValue = null;

    this.undo = function() {
        if (this.current > 0) {
            This.rawValue = this.history[this.current-1];
    this.redo = function() {
        if (this.current < (this.history.length - 1)) {
            This.rawValue = this.history[this.current];
    this.enter = function() {
        this.startValue = This.rawValue;
    this.exit = function() {
        if (this.startValue != This.rawValue) {
            this.history.length = this.current;
            this.history[this.current] = This.rawValue;

With this code in place, the enter event can call:


Similarly, assuming the text field is named “Test”, the click event on an undo button can call:


This is all pretty powerful, elegant stuff, and it’s tempting to adopt this form design pattern. But as I said — the side effects are a problem.

There are two possible ways to revise the form so that it works in strict scoping:

  1. Move the expando properties to script objects
  2. Re-code the logic so that it doesn’t use expando properties

Move the expando properties

The solution in this case consists of moving the logic from the field to a script object. A by-product of the move means we have to have a referencing mechanism from the script object back to the fields. (note that the strategy of moving expandos to script properties will work only in Reader version 8.1 9.0 and later)

The steps to modify this form to allow it to work under strict scoping.

1) Move the logic to a script object

In the original form, we added an instance of the control function to each field.  In our revised design, we’ll move the instances of the control function to a script object and maintain a mapping between each field and its corresponding control function.

I added a script object called “ctl” and moved the logic from the initization event there almost verbatim:

var This;

var control = function() {
    this.history = [This.rawValue],
    this.current = 0,
    this.startValue = null;

    this.undo = function() {
     . . . .

The difference is that in the initialization script I created an instance of the function (with the new operator) right away.  Whereas in the script object we simply declare the function and will create instances of it later.

2) Add some bookkeeping

I’ve added some bookkeeping functionality to the script object.  This “glue” code allows us to keep our original logic intact by providing a mapping from fields to the corresponding instance of the control object:

// The fieldList object is a place to store instances
// of the control function for each field
var fieldList = {};

// We need to assign unique id's to each field.
// The unique ids will index into fieldList.

// Use nUID as a global counter to assign id's
var nUID = 0;

// register a field and create an id, and an instance of control
function register(fld) {

    // We'll store the id in the field under "extras"
    var UID = fld.extras.nodes.namedItem("UID");
    if (UID === null) {
        UID = xfa.form.createNode("text", "UID");
    UID.value = "ID" + nUID.toString();
    This = fld;

    // create an instance of the control function
    fieldList[UID.value] = new control();

// If a field is part of a subform that gets removed, remove it here also
function unregister(fld) {
	   delete fieldList[fld.extras.UID.value];

// Convenience methods for accessing the control functionality
// associated with each field
function getObj(field) {
   return fieldList[field.extras.UID.value];
function enter(field) {
    This = field;
function exit(field) {
    This = field;
function undo(field) {
    This = field;
function redo(field) {
    This = field;

3) Change the code references

The syntax of code that called methods directly on the Test field now needs to change. The initialization script of the Test field now makes a call to:


The enter event changes to:



The click event is now:



4) Cleanup

When the control function was hosted by the Test field, the instance was conveniently removed when the Test Field was removed.  But now this needs to be done explicitly. The code to remove the subform has a new call:


After following the steps above, the revised form works under strict scoping.  Check it out.

Recode the Logic

But now the question needs to be asked: was this the best way to convert the form?  The process I followed was geared to keeping the control logic intact.  My assumption was that this is where the customer has invested the most and they’d prefer to keep that code intact.  But the implementation of the control function could have been implemented differently.  The history is represented as a JavaScript array, but could be re-implemented as an array of elements under <extras>.  The final solution is more elegant — but the effort to get there is riskier.

I’ve attached a 3rd variation of the form that implements undo/redo using extras. I won’t go into details on how it works here.  But one of the interesting side-effects of using extras rather than JavaScript variables is that due to formstate functionality, the undo history is preserved when the form is closed and saved. i.e. you can re-open the form later and the undo history is intact.


9 Responses to Sample: Convert to Strict Scoping

  1. Keith Gross says:

    Good post. Strict scoping conversion can be difficult and this will certainly help people. I think that you made one small mistatment though. You say that this technique will work in Reader 8.1 and above. But I thought that though 8.1 supports strict scoping it is overly aggressive in the releasing of script scope variables. As stated in the blog post you reference at the start ALL Javascript variables are cleaned up after each script execution. This means that all your XFA script objects must be re-entrant.

    In the above example code wouldn’t the fieldlist variable be getting re-initialized all the time thus losing track of your control objects?

  2. radzmar says:

    Impressive! Really Impressive!

    Just one thing.
    In the third sample I saw, that the undo doesn’t restore the initial value correctly.
    It’s always “1” in all of the fields, no matter which was the initally value. Hm? I could not resolve the mystery. Any Idea about this?

    • John Brinkman says:

      Glad you liked it.
      And I’m sorry you found a bug.
      It’s fixed. The problem was that I didn’t push the initial value onto the history stack.
      And then undo populated the field with the value of history.current
      I’ve updated the sample with a fix.

  3. radzmar says:

    Could you explain what you’ve changed?
    I don’t see a difference in the third sample.

    I believe the enter function needs to be changed in a way similar to the exit function.
    But, when I copy the part to store the undo history it indeed works, but all values then are stored twice and the first item is still “1”.

    • Radzmar:
      You’re right, it wasn’t quite working as it should. Turns out the problem is that the form was targeting 8.1 and the script object was not re-entrant. It relied on startValue to preserve its value. Moving the form to 9.0 has fixed the issue.


  4. radzmar says:


    I used another browser for downloading, looks like Chrome permanently hoaxed me with download from its cache. :-/
    It works like a charm now, brilliant!
    Btw: This is the first example of the extras object that gives me an idea, of what this object is good for, I never could imagine what the manuals tries to tell me about.

    PS: You wouldn’t happen to know what can be done with those delta objects?


  5. radzmar says:

    Oh yes John, this offers me a whole new world of possibilities.
    I’m now able to dynamically create a list of salts for a hash encryption stored somewhere hidden in the form .