Archive for June, 2011

Code Sharing Example

Today I’d like to talk about code sharing within form definitions. We’ll start by talking about the advantages of code sharing and then get specific about techniques to support code sharing in form definitions. We’ll start with the advantages.  For those of you with a programming background, this is review.  But bear with me:

  • Less code.  Once you have a library of shared code, you will write less code in your event scripts.
  • Faster development. Writing less code means faster code development.
  • Lower maintenance costs.  Less code means les code maintenance
  • Consistent behaviours within a form and between forms.  Shared code means that the same action from different places in the same form or between different forms will behave consistently. And it means that changing the behaviour is done by modifying the centralized logic.
  • Simplify complex operations for use by novice form designers.  A shared code library can offer simple interfaces to complicated functions.

But … to get the benefits of code sharing you need to make an investment:

  • Recognize repeated patterns
  • Generalize the repeated operation
  • Isolate the variable parts of the operation into parameters

The generalized, shared version of functionality has higher up-front development costs.  However once the initial investment has been made in shared code, the ROI will more than compensate.

Ok, enough of talking about things you already knew.  Lets walk through a specific example.  I reviewed a form recently that had lots and lots of code to copy field values between subforms. The sort of thing you might do if, for example, you were copying a billing address to a shipping address.

Assuming subforms named S1 and S2 and fields named F1 – F5, the code initially looked like this:

S2.F1.rawValue = S1.F1.rawValue;
S2.F2.rawValue = S1.F2.rawValue;
S2.F3.rawValue = S1.F3.rawValue;
S2.F4.rawValue = S1.F4.rawValue;
S2.F5.rawValue = S1.F5.rawValue;

As written, this is not very good code.  It’s verbose and tedious.  And if any fields are added, removed or renamed, the code will need to be changed.

Now, let’s look at a progression of changes we can make to improve this code.  First of all, a little JavaScript tip. When you see an expression such as S2.F1, it can also be written as S2["F1"].  With that knowledge, we can re-write the script as:

var fields = ["F1", "F2", "F3", "F4", "F5"];

for (var i = 0; i < fields.length; i++) {
   S2[ fields[i] ].rawValue = S1[ fields[i] ].rawValue;
}

It’s a little less verbose, but still fragile.  Let’s change it so that our list of fields is not hard-coded:

var srcFields = S1.resolveNodes("$.#field[*]");
for (var i = 0; i < srcFields.length; i++) {
  var fieldName = srcFields.item(i).name;
  // if the same-named field exists in S2…
  if (S2.nodes.namedItem(fieldName)) {
     S2[fieldName].rawValue = srcFields.item(i).rawValue;
  }
}

Notice that the script starts by using resolveNodes() to get a list of fields from the source subform. It then checks if the same named field exists in the destination subform.  If it’s in both places, we copy a value over. This is a big improvement.  It means that if any fields are added, removed or renamed the script will continue to work.  But we’re still not sharing code.

The next step is to generalize the function:

function subformCopy(dst, src) {
  var srcFields = src.resolveNodes("$.#field[*]");
  for (var i = 0; i < srcFields.length; i++) {
    var fieldName = srcFields.item(i).name;
    // if the same-named field exists in S2…
    if (dst.nodes.namedItem(fieldName)) {
      dst[fieldName].rawValue = srcFields.item(i).rawValue;
    }
  }
}
subformCopy(S2, S1);

Here we’ve isolated the copy functionality into a function. The next step is to move that function into a script object.  Now our script is a one-liner:

utilities.subformCopy(S2, S1);

Great! we copy subform contents in one line of script.  Now to really increase the value, take the script object and make it a fragment so it can be shared between forms:

Once the function has been shared, it can be enhanced or fixed and all forms using it will benefit.  In this case we might choose to make the subformCopy() method handle fields in nested subforms.

Up to this point I’ve talked about using script objects for code sharing.  There are a couple more things to say:

  1. I see people using execEvent() to share code.  e.g. they put script in a change event and use execEvent to call it from the initialize event. I don’t favour this pattern: The code readability is poor.  The performance is not as good (much less overhead to call a script object than it is to call execEvent). 
  2. Propagating events offer another code sharing technique. Isolating functionality in a propagating event means that you write the code in only one script and it is reused in many fields.

And one last point — do you hard-code color values or border widths in your code?  Consider moving these hard-coded values into script objects or form variables. The impact isn’t as dramatic as with shared code, but it is good practise.

 

resolveNode vs. resolveNodes

Today I’d like to poke at a design pattern I see fairly often.  The code looks like this:

var sum = 0;
var numberOfDetails = po._detail.count;
for(var i = 0; i < numberOfDetails; i++) {
    var nodeName = "po.detail[" + i + "].subtotal";
    sum += this.resolveNode(nodeName).rawValue;
}
            

Notice how inside the loop we’re constructing a SOM expression to use for resolveNode(). The constructed SOM expression will look something like: po.detail[2].subtotal.

Let’s look at the alternative:

var sum = 0;
var details = this.resolveNodes("po.detail[*].subtotal");
for (var i = 0; i < details.length; i++) {
    sum += details.item(i).rawValue;
}

The second variation is easier to read, easier to code and will be processed more efficiently.  Why didn’t the author code it this way to start with? Likely either: a) they didn’t realize that a single SOM expression could find all their nodes or b) they didn’t realize there is both resolveNode() and resolveNodes().  And since I don’t know the reason, let’s unpack both of those topics.

SOM expression to return multiple nodes

Look again at the second SOM expression: po.detail[*].subtotal. As this SOM expression is processed, it will first find all the detail subforms under po, and then for each of those detail subforms it will check for a child named subtotal and add it to the returned list.  I suppose what’s surprising is the realization that the wildcard can go anywhere in the SOM expression.  In fact, it’s permissible to have multiple wildcard expressions.  Suppose I had a form with a list of family members who each had a list of pets.  To get a list of all the pet names, I could use this expression: family.member[*].pet[*].petName

resolveNode vs resolveNodes

The difference between these methods is in what result you’re expecting. resolveNode() expects to process a selection that returns a single node. If you pass it an expression that returns multiple nodes, it will cause an error.  The return value of resolveNode() is either a node or null.

resolveNodes() expects to return a list of nodes.  Its return value is a nodelist — which could be empty.  The nodelist has two methods for traversal: list.length (return the number of nodes in the list) and list.item(n) return the nth item.

And while I’m on the topic, there are a couple other interesting things about these methods.  They begin their search from the context of the node they’re called from.  That means the result of total.resolveNode() will be different from the result of xfa.resolveNode().  For example, suppose my form/data has the structure:

<purchaseOrder>
  <po>
    <item>
      <subtotal/>
    <item>
    <item>
      <subtotal/>
    <item>
    <item>
      <subtotal/>
    <item>
    <total/>
  </po>
</purchaseOrder>

 

For the total calculation, we want a list of all the subtotal fields.  If we anchor the search from xfa, it looks like:

xfa.resolveNodes("form.purchaseOrder.po.item[*].subtotal");

If we anchor it from within the total calculation, it looks like:

total.resolveNodes("item[*].subtotal");

The second variation is preferred.  It’s easier to read, and since it references fewer nodes, it’s more durable — less susceptible to breaking if a node gets renamed or a subform added/removed. 

By the way, Niall O’Donovan (active contributor in the forums and a regular commenter) has written a blog post that does a great job of explaining SOM expression: http://www.assuredynamics.com/index.php/2011/05/som-expressions/. Niall has a gift for explaining the basics of the technology. The post includes a very spiffy sample form that helps visualize SOM expressions. (he also constructed SOM expressions, but he’s certainly not the only one…)

The Deep End

There are a few more things that could be said to round out the picture for those of you who like to dive one layer deeper.

The .all property

The object model has a couple of properties that can be used in place of a wildcard SOM expression. The .all property will return a list of all sibling nodes that have the same name. I could also have used the .all property in my original sum calculation:

var sum = 0;
var details = po.detail.all;
for (var i = 0; i < details.length; i++) {
    sum += details.item(i).subtotal.rawValue;
}

Of course, .all is not as powerful as [*].  We can’t code po.detail.all.subtotal.  The other problem with this calculation is that it presumes there’s at least on instance of the detail subform.  If there are no instances, the script will fail.

We also have the .classAll property to return all sibling nodes of the same type. If you use .classAll on a field, you will get a list of all the sibling fields. For example po.detail.classAll is the same as po.resolveNodes("$.#subform[*]")

Evaluation in scope

A relative SOM expression will search through the entire scope to find the result. In the example: total.resolveNode("item[*].subtotal") we will search for item among the children of all the ancestors in our hierarchy.  Specifically, the search will check the children of: total, po, purchaseOrder, form and xfa.  Of course, we stop searching as soon as we find a match. In this case we’d stop at po.  If you want to prevent this hierachy search, then you can anchor the search with a reference to "$" (the current node).  In our example that would look like:

total.resolveNodes("$.parent.item[*].detail");

Rule of Thumb

If you find yourself constructing a SOM expression with string concatenation, I’d encourage you to have a second look and see if there is an easier way to get the result. I think it’s rare to need to construct an expression.  The one exception is if you’re building an expression that uses a predicate.  But other than that case there is almost always an alternative.

Propagating Events

Back to another of the 9.1 enhancements intended to help you write less code: propagating events. See here for an overview.  Again, now that we have a Design tool capable of authoring these and now that the 9.1 Reader is more common, we should have a closer look. 

For starters, here is the sample form I’ll be referencing.  As you try the form you will notice a couple of behaviors:

  • as you move your mouse over fields, they are highlighted
  • invalid fields get a thick red border

The punchline here is that there are 14 text fields on the form, but only one each of a mouseEnter, mouseExit and validationState event scripts.  These events are defined on the root subform and apply to all descendent objects.  This is a great technique for minimizing code in your form.  A highly recommended form design technique. 

But now the bad news… So far, this functionality has not been easy to get at through Designer.  In the ES2 designer there was a check box to make an event propagate.  But the script dialog didn’t allow field events to be defined in the context of a subform.  Then because of the confusion this functionality caused, the checkbox was removed from the Desginer that shipped with Acrobat 10.  This functionality should re-emerge in a future version of Designer. 

In order to make it easier to get at this functionality, I have written a macro that will add propagating events to subforms.  The UI appears like this:

After you run the macro, the event and script will be added to your subform. Now, the tricky part — to edit the script you need to do two things: 1) close/re-open the form. Yeah, macros are still beta, and Designer doesn’t pick up on the changes made by the macro. 2) on the script dialog for the subform, choose to show: "events with scripts".

One interesting thing to note is that it is valid to have two or more of the same event defined on a subform. e.g. you could have both a propagating enter event as well as a non-propagating enter event.

The Deep End

When changing the display of an object to show an error state, it’s a bit of a challenge to revert the object back to its original state.  e.g. If you change the border color or border thickness you need to know the original color and thickness in order to restore the original state of the field.

Well, not really.  You’ll see the validationState script in the sample uses a different technique to restore the original display.  The script removes the border and assist elements from the field.  To understand why this works you need to understand the relationship between the form and the template.  The template is the definition of a form you see in Designer.  At runtime, we merge the template objects with data and create form objects.  A form field is then a sparsely defined object that has a pointer back to a template field. 

When we execute script that changes the color of a border, we modify the form definition of the border and override the default border properties found in the template. Removing the border from the form field means that we have removed the override and the field then reverts to the definition found in the template.

 

Border Control

In these times. many countries are interested in border control.  Today I’m interested in controlling field borders. Did you know that fields have two borders? There is a border around the entire field (including the caption) and there’s a border around just the content part — excluding the caption. These are edited in two different places in the Designer UI.  Field border is specified under the border tab:

The border around the content portion is specified in the "Appearance" selection of object/field:

Both of these borders can be manipulated in script.  Have a look at this sample form to see what I mean. 

You might notice that the scripts in the sample form do not use the field.borderColor or field.fillColor properties. These are shortcuts — convenience syntax that simplifies the underlying property structure. And while they’re convenient, they don’t give you full control. Most notably, they control only the outer border, do not give access to the widget border. 

As you look at the script in the sample form, you will notice some interesting things:

1) script reflects syntax. 

The (simplified) XML definition of a border looks like this:

<border presence="visible | hidden">

    <edge presence="visible | hidden" thickness="measurement">
        <color value="r,g,b"/>
    </edge>  <!-- [0..4] -->

    <fill presence="visible | hidden">
      <color value="r,g,b"/>
    </fill>
</border>
            

A script that changes a border fill color (border.fill.color.value = "255,0,0";) is simply traversing the hierarchy of elements in the grammar.

2) Edge access is tricky

The syntax to get at the four edges uses the "getElement()" method, whose second parameter is the occurence number of the edge.  Note that the sample always sets all 4 edges.  This is because edge definitions are inherited.  e.g. if only one edge is specified, then modifying the single edge property impacts all four edges.  The problem is that you don’t always know how many edges have been specified, so it’s safest to set all four explicitly.

3) Show/hide with the presence property. 

You’re likely accustomed to using the presence property of fields. That same attribute applies on the components of a border:

border.presence = "hidden";      // hides entire border — all edges and fill
border.fill.presence = "hidden"; // makes the border fill transparentborder.getElement("edge", 0).presence = "hidden"; // hides the first edge

4) There’s more.

There is more to the border definitions than I’ve shown you here. Using script you an control the four corners, fill patterns, edge thickness, border margins and more.

5) There are other borders

Subforms, rectangles, draw elements and exclusion groups all have border definitions.

A better validation pattern for 9.1 forms

Today I’d like to go back again to revisit some functionality introduced in Reader 9.1 (XFA 3.0).  In 9.1 we added some enhancements specifically designed to improve the form validation user experience — and to allow validations to happen with less JavaScript code.  Specifically:

  • Message Handling options (described here)
  • The validation state change event (described here)

Prior to these enhancements, users were avoiding the use of validation scripts because they didn’t like the message boxes that popped up on every validation failure.  But now we have control over those message boxes, and, as we’ll see, there are lots of good reasons to like using validation scripts.

First things first, turn off the message boxes.  There’s a new designer dialog for this:

Designer dialog box showing a setting where the author can specify that validations do not display message boxes.

Great! now we don’t get those pesky dialogs showing up every time.  But now, of course, it’s up to you to find some other way to convey to the user that they have invalid fields in their form.  This is where the validationState event comes in.  It fires when the form opens, and it fires again every time a field validation changes from valid to invalid or vice versa.  The expected use of validationState is to change the appearance of the form to flag invalid fields. 

One more important piece of the puzzle: field.errorText. When a field is in a valid state, this text is and empty string.  When the field is invalid, it is populated with validation error text.

Take the example where we expect a numeric field to have a value greater than zero.  The validation script looks like this:

encapsulation.A1.B.C.D::validate-(JavaScript, client)
this.rawValue> 0;

The validationState event will set the border color and the toolTip text:

encapsulation.A1.B.C.D::validationState-(JavaScript, client)
this.borderColor = this.errorText? "255,0,0" : "0,0,0";
this.assist.toolTip.value = this.errorText;

Setting the toolTip value is important for visually impaired or color-blind users who won’t notice a change in border color. (There’s another topic waiting on designing a good validation experience that works with assistive technologies).

Hopefully this looks pretty clean and easy to code.  It’s important to contrast this approach with the alternative — just to make sure you’re convinced.

The Alternative

The alternative to field-by-field validation is to centralize form validation in one big script.  The big validation script then gets invoked by some user action – usually a form submit or print or validation button. Working with the same example, this script would look like:

encapsulation.validate::click-(JavaScript, client)
var field = A.B.C.D;
var bValid = field.isNull || (field.rawValue> 0);
if (bValid) {
     field.borderColor = "0,0,0";
     field.assist.toolTip.value = "";
} else {
     field.borderColor = "255,0,0";
     field.assist.toolTip.value = "The value of D must be greater than zero";
}

Here is a sample form that has both styles of validation.  And here are some reasons why you should prefer field-by-field validation:

  • Less code.  Three lines of code vs. nine. In a form with hundreds of fields, this difference becomes compounded
  • Better user experience — immediate feedback when fields become valid or invalid
  • Form processor is aware of the invalid field and will automatically prevent form submission
  • External validation scripts need to also enforce null/mandatory fields.  In the field-by-field approach, mandatory is enforced without any script.
  • Encapsulation: A term from our object-oriented design textbooks.  In this context it means that the field definition (including business logic and messages) is self-contained.  The benefits of encapsulation include:
  • Notice that the second example references the field by it’s SOM expression: A.B.C.D; There are any number of edit operations that could change that expression: moving a field, unwrapping a subform, renaming a subform or field etc.  If any of these operations happen, the script will need to be updated.  In the first example, there is no SOM expression needed and the script can remain the same through all thos edit operations.
  • Use fragments.  If you want the field to be included in a form fragment, you need to make sure that the validation logic is included with the field.  When the logic is encapsulated, this is not a problem.  When the validation logic is outside the field, it’s much harder to find ways to have the logic accompany the field.

Not Ready for 9.1?

Assuming you’re now convinced that field-by-field validation is what you want, you might still be in a situation where you can’t assume your users have 9.1.  In that case, I’d encourage you to check out some of my older blog posts that included a script framework that allowed field-by-field validation without the 9.1 enhancements.  The most recent version of that framework was included in this blog post.

Understanding Field Values

Knowing a bit more about how field values are represented in JavaScript could make a difference in how you write script.  Today I’ll give an overview of how field values are processed.   For starters, here is a sample form that will illustrate some of the points I make below.

But first, we need to be mindful that picture clauses (patterns) impact our field values.  A brief recap of the various picture clauses (patterns) we use:

  • format pattern: Used to format field data for display
  • edit pattern: Used to format field data when the user has set focus in a field and is about to edit a value.
  • data pattern: Used to format field data when it is saved to XML.
  • validation patten: Used for field validation — does not impact field value

With that background, let’s look at the properties available:

field.rawValue: the field value in its raw form. i.e. a format that has no patterns applied and a format that is consistent across locales and is suitable for calculations.

field.editValue: The field value when formatted with the edit pattern. If there is no edit pattern you will get a reasonable default — usually the rawValue, except in the case of a date field where you’ll get some locale-sensitive short date format.  If a field value cannot be formatted using the edit pattern, then field.editValue will revert to be the same as field.rawValue.

field.formattedValue: Same as field.editValue except using the display pattern.

Some miscellaneous facts about field values:

  • field.editValue and field.formattedValue always return a string. If the field is null, these will return an empty string
  • rawValue returns a JavaScript type corresponding to the kind of field.  e.g. A text field is a string, a numeric/decimal field is a number
  • when a field is empty, rawValue will always be null
  • rawValue is what will be stored in the XML by default (assuming no data pattern has been specified)
  • There are two ways to check for a null value:
    • field.rawValue === null
    • field.isNull
  • The rawValue of a date field does not have a type date.  We chose to represent dates as a string — in the form that they will be saved.  The format used for the string is YYYY-MM-DD.
  • If you use JavaScript typeof to determine what kind of a value a field has, be aware that typeof(null) returns “object”

Knowing all this, there are some implications for the code you write:

  • If you need a JavaScript Date from a date field, you can use this function:
    function ConvertDate(sDate) {
       if (sDate === null) {
          return null;
       }
       var parts = sDate.split("-");
       // Convert strings to numbers by putting them in math expressions
       // Convert month to a zero-based number
       return new Date(parts[0] * 1, parts[1] - 1, parts[2] * 1);
    };
                
  • You should never code: field.rawValue.toString()
    This will result in a JavaScript error when the field is null.
  • If you want an expression that always returns a string, never null, use field.editValue or field.formattedValue
  • I often see code in the form:
    if (field.rawValue == null || field.rawValue == “”) { … }
    This is unnecessary.  Use one of the following:
    if (field.isNull) { … }
    if (field.rawValue === null) { … }
  • If you prefer not to use a validation pattern, you can validate a field using the display picture.  Use this validation script:
    this.formattedValue !== this.rawValue;