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.
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.
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
}
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.
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).
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).
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.
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.
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.
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.
I thought it was pretty quiet around here. About a month ago we updated our internal blogging infrastructure. Coincidentally, I haven't seen any comments on my posts since then. Well, turns out 100% of the comments were flagged as spam. I have gone into the spam bucket and approved all your comments. I will reply to them over the next couple days.
Sorry for the inconvenience. And thanks for the commenting -- I really appreciate hearing your perspectives on the technology.
When I was working on my previous blog entry, I struggled a bit with rendering JavaScript source code in fields. The issue was how to accurately display text with the tab stops that are embedded in the code. The goal was to have the JavaScript code appear with the same spacing you would see in the source editor. For this to work, the tab spacing needed to be exactly 4 characters. The solution involved:
Having accomplished that much, it seems worthwhile to spend some time showing some of the other cool things you can do with tab settings in fields. In Acrobat/Reader 9 we introduced tab leaders. To see how this feature works with static text, check out Stephanie Legault's tutorial.
That's fine for static text, but what about fields? If you want to use tab leaders in your form fields, you need to understand the field properties that control tab behaviours. One way to get there is to read the relevant portions in the XFA specification. Tab settings are paragraph properties. A field's paragraph properties are defined in the <para> element (Part 2/Template Specification/the para element). Now, you could fill your head with all that book knowledge or you could code from examples.
The first page of the sample shows some variations on tab settings. Each sample field has an initialization script to set the tab properties. In each case there is a corresponding field that displays the resulting <para> element syntax.
This property on the <para> element controls the default tab setting. If you want a tab stop every half inch, this would look like:
<field>
<para tabDefault="0.5in"/>
</field>
To set this via script:
form1.tabExample1.sample::initialize - (JavaScript, client)
this.para.tabDefault = "0.5in";
The general format of a tab leader is: "[alignment] [leader] location ". You can string a bunch of these together to specify multiple tabs. If you look at the sample form you will see some variations. The alignment ( left (before) / right (after) / center / decimal ) determines how the text is aligned at the tab location. The leader parameter determines how to fill the space leading up to the tab position -- space, dots, rule, characters. The 3rd parameter is the actual tab position. Hopefully it's all self-explanatory from the samples.
So far, this discussion has been assuming that we're rendering data that has embedded tabs. Do tab settings work with interactive fields? Well, yes and no. The tab settings work fine. The problem is, how exactly do you enter a tab character into an interactive field? The tab key moves you to the next field. The workaround the example uses is to define a key sequence that will insert a tab character. Pressing "shift" + "space" will insert a tab character. The way this works is that the field has a change event to translate this sequence:
form1.#subform[0].tabExample1.sample::change - (JavaScript, client)
if (xfa.event.change === " " && xfa.event.shift) {
xfa.event.change = "\t";
}
Rich text introduces a couple of wrinkles wrt tabs.
The tab character is not handled by XHTML. XHTML processing rules tell us to collapse all white space down to a space character. To get past this, we define a special style attribute to specify a tab:
<span style='xfa-tab-count:1'/>.
The attributes that exist on the <para> element are also available as style properties: tab-interval and tab-stop. With a plain text field, the paragraph properties are constant for the whole field. With rich text, the paragraph properties can be re-specified with each <p> element.
The second page of the sample form has an example where a table of values is formatted into a plain text and a rich text field. To see the code, look at the form:ready event of each field. The script for the plain text variation is much simpler, but the script for the rich text field does some more elaborate formatting. But back up a moment. Likely the idea of constructing rich text is new to some of you. The technique is fairly straightforward -- build a string representing the XHTML content and then use the loadXML() method to apply the text to the field. Using XHTML allows the second field to enhance the output:
The overall effect makes the second menu field a fair bit nicer. Imagine what someone with artistic skills could do.
Did you notice? The dots in the plain text menu line up with the dots used in the rich text menu. By default, the dot leaders are aligned between different objects on the page. i.e. the dots are aligned on a grid defined at the page level.
When the rich text is rendered, the display properties are determined by combining the field properties with the properties specified in the XHTML. An example would help:
<field>
<font typeface="Myriad Pro"/>
<value>
<exData contentType="text/html">
<body>
<p>Text 1<span style="font-family:Arial">Text 2</span></p>
</body>
</exData>
</value>
</field>
In this example, the XHTML has not specified a font for the string "Text 1". Whereas the string "Text 2" is explicitly styled as Arial. This means that the font for "Text 1" is the ambient font -- the font inherited from the field definition -- in this case Myriad Pro. Same applies to tab settings. By default the XHTML will inherit the tab settings from the field. If you want to deviate from the default, then specify it inside a <p> element in the rich text. That explains why in my rich text menu example I explicitly styled the first paragraph, but allowed the remaining text to inherit the field paragraph properties.
XHTML strings can grow very large. You need to be careful how you build the strings. Using the " += " operator can lead to performance issues. Appending to a large string is expensive. It breaks down to these set of operations:
The larger the string and the more append operations, the more expensive this becomes. A more efficient alternative is to build an array of small strings and at the end, use the join() method to generate one large string. Here are two examples for comparison:;
var S = "hello world";
S += " more text";
S += " even more text";
In comparison:
var StringArray = [];
StringArray.push("hello world");
StringArray.push(" more text");
StringArray.push(" even more text");
var S = StringArray.join("");
Of course, in this specific example, the strings are so small that the performance difference would not be noticeable. But when the number of append operations grow, the array technique will become much faster.
I have recently discovered JSLINT (www.jslint.com). This is lint processing for JavaScript. For the under-40 crowd, lint is a unix utility we used back in the days when C compilers were not strict. Used to be that you could do things in C that were blatantly wrong -- and detectable -- but the C compilers and linkers happily gave you enough rope to hang yourself. lint analyzed your C source code, reported errors, and encouraged you to make your code more strict.
Now we have JavaScript. A wonderful utilitarian language that also has a ready supply of hanging rope. A sizeable share of our JavaScript programming errors happen because we inadvertently make use of the laxness that is part of JavaScript.
This is where JSLINT comes in. JSLINT is a utility (written in JavaScript) that will analyze your script and report on errors it finds. When you visit www.jslint.com you can paste your code into the HTML page, and you will get a report of errors and other information about constructs in your script. JSLINT will continue to complain about your code until you have reduced it to a strict subset that can be used reliably.
I ran some of the code I've written recently through JSLINT and I was... embarrassed. I was determined to make all my code strict. (Hard to find a better motivator than embarrassment.) The problem is that copy/pasting code from Designer to www.jslint.com got pretty tedious. Happily, the author of JSLINT (Douglas Crockford) has made the source code shareable. I took the source for JSLINT and incorporated it into LintCheck.pdf. Clicking the button on the form will prompt you for a PDF or XDP file. It will extract all the script, run JSLINT and report on what it finds. Here is a sample form with some coding errors and the resulting report. (You need Acrobat to run LintCheck.pdf.)
One of the things I quickly discovered is that JSLINT reports many issues that are not necessarily related to language/grammar strictness, but rather are more a matter of enforcing good programming style. Good programming style in turn reduces the likelihood of bugs. eg. At first I was annoyed when JSLINT insisted that all my control blocks be enclosed in braces, but I do understand how that coding practise makes source code more predictable -- especially when you consider the potential ambiguity of an expression such as: "if (..) if (..) else (..)"
Some more details on using LintCheck.pdf:
In the script output, global references are highlighted in blue. If you see something highlighted in blue that is not a reference to a form object, then it is likely an undeclared variable. If you want to turn off this highlighting, insert a special comment in your code. e.g. /*global Price Quantity */ will turn off highlighting on script references to the Price and Quantity fields. commonly referenced globals such as "xfa", "console", "app" etc. have already been turned off.
If you look at www.jslint.com you will see a number of options that make JSLINT more or less strict. Some of those options are relevant only to JavaScript embedded in HTML pages. The more generic options are included in LintCheck.pdf. When I check my code, I turn on the options for "Strict white space", "Disallow undefined variables", "Disallow == and !=" and "Disallow bitwise operators".
If you want specific options to be enforced selectively in your code, you can control them with comments. For example, to turn on white space checking, insert this comment:
/*jslint white: true, indent: 4 */
To help you build up this string, there is a field on the form that shows what the comment would look like for the chosen options.
Script writers who use variable names that conflict with XFA properties or methods will run afoul of the "naked field processing" (as explained here). Since JSLINT provides a list of variables and global references, LintCheck.pdf will report cases where users have a conflict in their script.
JSLINT is unhappy with combinations of spaces and tabs. If your line of code starts with 2 spaces followed by a tab, most code editors will bring you to column 4. JSLINT assumes this to be column 6. It assumes that a tab character always adds 4 spaces. If you mix tabs and spaces you will get complaints from JSLINT when strict white space is on. You will also find that the character position of errors is reported incorrectly.
Lint processing does not apply to FormCalc. But since LintCheck.pdf produces a script report, it seemed best to allow FormCalc scripts to be included in the output. There is an option to control whether FormCalc is included or not.
Suppose you have a calculation that looks like:
form1.#subform[0].check[6].result::calculate - (JavaScript, client)
Price.rawValue * Quantity.rawValue;
JSLINT will generate an error: "Expected an assignment or function call and instead saw an expression.".
However, this is a valid construct in our forms. Calculations and validations are the result of the last expression evaluated in a script. The version of JSLINT embedded in LintCheck.pdf adds a new option to control this behaviour. To turn this check on (e.g. for script objects) add this directive in your script: /*jslint nonoop: true */
One reason to turn this check on is because for forms to work in Form Guides, calculations must be expressed as an assignment. e.g.
form1.#subform[0].check[6].result::calculate - (JavaScript, client)
this.rawValue = Price.rawValue * Quantity.rawValue;
One common JavaScript coding error is to misspell a member name. Look at this sample script:
var fruits = ["apple",
"orange",
"peach",
"plum"];
// truncate the array
fruits.lenght = 2;
The intention was to truncate the array to 2 -- but it doesn't because I misspelled the length property. The script runs without a runtime error -- because JavaScript assumed we wanted to populate a new property called "lenght". The solution here is to add a comment at the top of your script listing all the members you expect to see:
/*members length rawValue */
Now JSLINT will complain about the offending line because the "lenght" property is not in the list of sanctioned members.
Forms with lots of script will generate very large reports. Very large reports take a long time to generate. Running LintCheck.pdf on itself takes around 15 seconds to complete on my desktop system (and generates 133 pages).
Unfortunately, the parser in JSLINT is not able to handle E4X expressions. Since LintCheck.pdf makes use of E4X to handle XDP files, I needed a workaround to avoid having JSLINT stop when it encountered my E4X expressions. Turns out there are JSLINT friendly variations. The script expression to find the root subform and script elements of an XDP originally looked like:
var sRootSubform = xXDP.*::template.*::subform[0];
var vScripts = vRootSubform..*::script;
The alternate expressions looked like:
var vRootSubform = xXDP.elements(
QName(null,"template")
)[0].elements(
QName(null,"subform")
)[0];
var vScripts = vRootSubform.descendants(QName(null,"script"));