Posts in Category "Scripting"

Populate a listbox from a web service

Jeff K asked for a sample form where we populate a listbox from the results of a web service.  He was even kind enough to point me at a set of public domain web services that could be referenced from a sample form.

The web services can be found at: http://www.webservicex.net

The sample I wrote is based on the USA Zip Code Information. Specifically the request to GetInfoByCity: http://www.webservicex.net/WCF/ServiceDetails.aspx?SID=35

The specific WSDL file can be found at: http://www.webservicex.net/uszip.asmx?wsdl

This query takes a city name as input, and returns xml data with all the zipcodes that match that city name — in each state that has a city with that name.

Here are the steps I followed to create the sample:

Create a Data Service

Once I downloaded a local copy of the WSDL file, I used it to create a data connection named CityQuery.  When I expanded the data hierarchy to look at the input and output data, I found one data field in the request area (USCity).  Good so far.  But I was expecting more in the response area.  All I found was: "GetInfoByCityResult":

image

Turns out this web service uses <s:any> element — which means Designer has no idea what data will be found under that node and cannot offer specific guidance for binding decisions.

If you run the sample at: http://www.webservicex.net/WCF/ServiceDetails.aspx?SID=35 , you will discover the data format by looking at the returned result:

<NewDataSet>
<Table>
<CITY>Mission</CITY>
<STATE>KS</STATE>
<ZIP>66202</ZIP>
<AREA_CODE>913</AREA_CODE>
<TIME_ZONE>C</TIME_ZONE>
</Table>
<Table>
<CITY>Mission</CITY>
<STATE>SD</STATE>
<ZIP>57555</ZIP>
<AREA_CODE>605</AREA_CODE>
<TIME_ZONE>C</TIME_ZONE>
</Table>
<Table> ... </Table>
<Table> ... </Table>
</NewDataSet>

Set up List Box Binding

I created a text field (City) and bound it to the request data: USCity.

Then I created a drop down list and set up bind to the returned data.

image

The binding wizard took me only as far as the GetInfoByCityResult element.  The rest I had to enter manually — based on what the sample data looked like.  The bind expression for items is:

!connectionData.CityQuery.Body.GetInfoByCityResponse.GetInfoByCityResult.NewDataSet.Table[*]

Note that the expression starts with "!".  This character is used in SOM as a shortcut to the dataset elements i.e. the elements below xfa.datasets.  The data exchanged with this web service is gathered under xfa.datasets.connectionData.CityQuery

Execute the WSDL

The last step is to make sure the SOAP request happens at the right time.  On the exit event of the City field I added this script:

form1.#subform[0].City::exit – (JavaScript, client)

// Invoke the web service

xfa.connectionSet.CityQuery.execute(false);

ZipCodes.selectedIndex = 0;

The form now works.  Every time you exit the City field, the SOAP request is made and the zip code list box gets populated.

The Deep End

I wanted to populate another drop down list with more than just the ZIP codes. I wanted to include city name and state name.  The hard part about this is that once the WSDL request completes, the transaction data is removed. The moment in time where you can access the returned WSDL data is in the postExecute event (preExecute fires before a web service request, and postExecute fires afterward).  But here is the problem.  Designer does not provide an interface to specify a postExecute script.  I had to add one in the XML source view:

<event activity="postExecute" 
       ref="xfa.connectionSet.CityQuery"

       name="event__postExecute">

  <script contentType="application/x-javascript">

    console.println(xfa.datasets.saveXML("pretty"));

  </script>

</event>

Once you’ve specified this event in XML source view, you can edit the script in Designer by selecting "Events with Scripts" in the script editor.

By specifying preExecute and postExecute events, you can access the SOAP data before and after the web service request. 

preExecute SOAP request:

<xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">

   <!– Form data (data bound to fields and subforms) –>

   <xfa:data>

      <f>

         <City>Mission</City>

         <ZipCode/>

         <Choice/>

      </f>

   </xfa:data>

   <!– The distilled WSDL schema used by XFA to construct the SOAP request –>

   <dd:dataDescription xmlns:dd=http://ns.adobe.com/data-description/

                          dd:name="CityQueryGetInfoByCitySoapInDD">

      <CityQuery>

         <soap:Body xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">

            <tns:GetInfoByCity xmlns:tns="
http://www.webserviceX.NET">

               <tns:USCity dd:minOccur="0" dd:nullType="exclude"/>

            </tns:GetInfoByCity>

         </soap:Body>

      </CityQuery>

   </dd:dataDescription>

   <!– The SOAP request –>

   <connectionData xmlns="http://www.xfa.org/schema/xfa-data/1.0/">

      <CityQuery xmlns="">

         <soap:Body xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">

            <tns:GetInfoByCity xmlns:tns="
http://www.webserviceX.NET">

               <tns:USCity>Mission</tns:USCity>

            </tns:GetInfoByCity>

         </soap:Body>

      </CityQuery>

   </connectionData>

</xfa:datasets>

Note that during preExecute, your script may modify the outgoing request data.

postExecute SOAP response:

<xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">

   <!– Form data (data bound to fields and subforms) –>

   <xfa:data> … </xfa:data>

   <!– The distilled WSDL schema used by XFA to construct the SOAP request –>

   <dd:dataDescription xmlns:dd=http://ns.adobe.com/data-description/ 
    …

   </dd:dataDescription>

   <!– The SOAP response –>

   <connectionData xmlns="http://www.xfa.org/schema/xfa-data/1.0/">

      <CityQuery xmlns="">

         <Body>

            <GetInfoByCityResponse xmlns="http://www.webserviceX.NET">

               <GetInfoByCityResult>

                  <NewDataSet>

                     <Table>

                        <CITY>Mission</CITY>

                        <STATE>KS</STATE>

                        <ZIP>66202</ZIP>

                        <AREA_CODE>913</AREA_CODE>

                        <TIME_ZONE>C</TIME_ZONE>

                     </Table>

                     <Table> … </Table>

                     <Table> … </Table>

                     <Table> … </Table>

                  </NewDataSet>

               </GetInfoByCityResult>

            </GetInfoByCityResponse>

         </Body>

      </CityQuery>

   </connectionData>

</xfa:datasets>

I was then able to write a script to populate the "Choice" drop down list with zipcode, city and state information:

var vTables = xfa.datasets.connectionData.CityQuery.Body.GetInfoByCityResponse.GetInfoByCityResult.NewDataSet.Table.all;

var aChoices = [];

for (var i = 0; i < vTables.length; i++) {

    var vItem = vTables.item(i);

    var sItem = vItem.CITY.value +

                  " \t" + vItem.STATE.value +

                  "\t" + vItem.ZIP.value

    aChoices.push(sItem);

}

Choice.setItems(aChoices.join(","), 1);

Choice.selectedIndex = 0;

New Reader 9.2 API

Last week Adobe released Reader 9.2.  Included with this release is a new API for loading file attachment data: util.readFileIntoStream().  The reason this method has been added is because certified PDFs cannot add file attachments.  This method allows the form author to embed attachments in their XML form data stream rather than as PDF file attachments.

util.readFileIntoStream(cDIPath, bEncodeBase64)

cDIPath

(optional) A device-independent path to an arbitrary file on the user’s hard drive. This path may be absolute or relative to the current document.  If not specified, the user is presented with the File Open dialog to locate the file.

If the cDIPath parameter is specified, this method can be executed only in a privileged context, during a batch or console event, or when the document is certified with a certificate trusted to execute "embedded high privileged javascript".

bEncodeBase64
(optional) If true, base 64-encode the file content. Defaults to false.

Returns

The File content as a ReadStream object that is optionally base 64-encoded.  If the user cancels the dialog, the method returns "undefined".

Example

The following sample shows how to use the API to load attachments.  The button script to load an attachment looks like:

var vStream = util.readFileIntoStream({bEncodeBase64:true});
if (typeof(vStream) !== "undefined") {
    var vNewAttach = _attach.addInstance();
    vNewAttach.contents.rawValue = util.stringFromStream(vStream);
}

Futures

It would be nice if the method provided the name of the loaded file.  It would also be nice if there was a corresponding util.writeStreamToFile() method.  I don’t know whether these enhancements are planned or not.

Populating List Boxes

When I occasionally browse around some of the LiveCycle forums, I frequently see questions around how to populate a drop down list.  I have put together a sample form that illustrates several different options.

Data Source

There are two basic sources for updating a drop down list definition: data binding or script.  If the list contents are defined as part of your form data, and if they don’t change during your form session, then use data binding.  If the definitions are more fluid, then use script.

The preOpen Event

The most important take-away from this blog entry concerns what event to use for updating lists.  I have seen customer forms that update list contents using change, exit, calculate, validate, mouseover, and enter events.  However, the proper place to do it is in the preOpen event.  The preOpen event fires when the user activates the control for the choice list.  Think of it as the "just-in-time" option.  If you try to maintain your list box definition from other events then often your script will update your list box contents too frequently. 

The only reason for updating a choice list sooner than the preOpen event is if you need to assign a value to a list field.  e.g. if your drop down list has a display value: "ONE" and a bound value: "1", then assigning
field.rawValue = 1; will cause the field to display "ONE".  Obviously this works only if the field has up to date list contents.  If you need your list contents updated more frequently, you should still put the list populating logic in the preOpen event, and use execEvent("preOpen") to populate the list from other contexts where it’s needed.

The sample form has four choice lists that get populated from their preOpen event, using data found in form field values, JavaScript arrays and XML data.

Script Commands

There are two script methods for setting your list box contents:
field.addItem()
   and
field.setItems()

The API: field.addItem() works in all versions of Reader.  field.setItems() was introduced in Reader 9.0 and is much faster and more convenient.  The sample form has script that illustrates how to use each method.

Binding to Data

I constructed some sample data (dogs.xml) that looks like this:

<dogs>
  <category file="ugliest">
    <rank>1</rank><dog>Chinese Crested</dog>
    <rank>2</rank><dog>Pug</dog>
    <rank>3</rank><dog>Shih Tzu</dog>
    <rank>4</rank><dog>Standard Schnauzer</dog>
    <rank>5</rank><dog>Chinese Shar Pei</dog>
    <rank>6</rank><dog>Whippet</dog>
    <rank>7</rank><dog>Dandie Dinmont Terrier</dog>
    <rank>8</rank><dog>Japanese Chin</dog>
    <rank>9</rank><dog>French Bulldog</dog>
    <rank>10</rank><dog>Chihuahuas</dog>
  </category>
  <category file="dumbest">
   …
  </category>
  <category file="comicBook">
   …
  </category>
  <category file="smartest">
   …
  </category>
</dogs>

On my form I want two drop down lists: one with the names of the categories and a second that gets populated with the contents of the category.  Since this XML is part of my data, I bound the category using these expressions:

binding

The second list has a preOpen event that locates the category in the data, and then populates a second listbox from the category contents:

// Find the data group that corresponds to the category chosen
// in the Category field

var vDogs = xfa.datasets.data.dogs.category.all;
var vChoice = null;
var i;
for (i = 0; i < vDogs.length; i++) {
    if (vDogs.item(i).file.value === Category.rawValue) {
        vChoice = vDogs.item(i);
        break;
    }
}
if (vChoice !== null) {
    // vChoice.dog.all is the equivalent of the
    //
SOM expression: "category.dog[*]"
    var vDisplayValues = vChoice.dog.all;
    var vBindValues = vChoice.rank.all;
    for (i = 0; i < vDisplayValues.length; i++) {
        this.addItem(vDisplayValues.item(i).value,
                    
vBindValues.item(i).value);   
    }
}

Performance

Populating a list from data is very efficient.  But populating large lists or many lists using addItem() can be slow.

The performance gains of setItems() over addItem() is substantial.  If your form makes extensive use of choice lists or has choice lists with large contents, you will appreciate the improvements of setItems().  Of course, this option is available only in forms designed for Reader 9 or later.

Web Services

In some cases, the definition of the choice list may be held on a server.  In this scenario, the best strategy is to add a WSDL connection to your form that retrieves the list contents.  Have your list box bind its contents to the data retrieved via the SOAP call.

Away for a week

After I eat lots of turkey on the weekend (Canadian Thanksgiving), I’m spending next week trying to empty the job jar at home.

Editable Floating Fields

Floating fields have always been a bit tantalizing because while they provide you with the output you want, many users want to be able to edit directly in the floating fields.  They want the field values nested inside the static text to be actual widgets.  Unfortunately, this is one of those enhancements that never seems to percolate to the top of the priority list (but don’t let that stop you from requesting this feature!).

In the mean time, I’ve worked up a sample that simulates the interactive behaviour.  Have a look here.  The way the sample works, it that it has two objects: the text object with the embedded fields and an interactive field that’s the same size, placed over top of the text object.  When the form initializes, it populates the interactive field with the content of the text element.  Then using the change event, it prevents the user from typing into the ‘boilerplate’ parts of the text but allows them to type into the field portions.

But before talking too much about the mechanics, I’ll describe how to re-use this code in your own form:

How to add the floating field editor to a form

1. Add the scEditFF and scXML fragments to your form.  These script objects hold all the logic needed to make the sample work. 

2. Add your draw element with floating fields as you normally would. 

3. Add a text field that allows multiple lines and also enables rich text.

4. Add initialize, change and exit events to your field:

form1.#subform[0].LetterEdit::initialize – (JavaScript, client)
scEditFF.initialize(this, Letter, "#c0c0c0", 10);

form1.#subform[0].LetterEdit::change – (JavaScript, client)
scEditFF.handleChangeEvent();

form1.#subform[0].LetterEdit::exit – (JavaScript, client)
scEditFF.handleExit();

The parameters to initialize are:

/**
* initialize a field to be a floating field editor for a given
* text object
* @param vField – the field that is displaying/editing the floating
* field content
* @param vText – the text object holding the floating fields.
* @param sBGColor – the color to shade the field areas.
* Specify as a CSS color value. e.g. #101010
* @param nEmptySpace – the number of spaces to use to
* render an empty field
*/

Behaviours

Some things you will notice when using this sample:

  • When the floating field editor initializes, it makes itself visible and hides the text object
  • You can’t tab between the floating fields.  Since all the fields are housed in the same widget, it means you tab in and out of the entire group.
  • Reserve space.  Notice when a field is empty and you type in, the space collapses to the character(s) you’ve typed.  Similarly, once you empty the field, it expands back to the reserved size.  Notice also that you cannot delete the reserved spaces.
  • Extra leading space.  There’s a (non-breaking) space at the beginning of each floating field.  This is needed to ensure that the user input is always rendered with the shaded field background.  When you type into rich text, the typed text always inherits the format of the character preceding the caret.  In order to make sure that content inserted at the beginning of a floating field gets shaded, I needed to add an extra (un-editable) space character.  I initially tried using an invisible character such as a zero width space, but found that in Acrobat 9 these special Unicode spaces are made visible when they’re part of field content.  I suspect that was done for security reasons.
  • Field Updates.  When you exit the widget holding all the floating fields, the exit event fires and will update all the fields that correspond to the editable areas.
  • The script turns off Acrobat’s field highlighting.  It just looks a bit weird when the entire widget holding the floating fields is highlighted.
  • Versioning.  Notice the sample has a version checking button.  It would not be surprising if I needed to update the script for bug fixes or small enhancements.

How it Works

Floating fields are represented by some custom XHTML syntax:

<span xfa:embedType="uri" xfa:embedMode="raw" xfa:embed="#FF011680"/>

The concept is fairly simple.  The form parses the XHTML, finding all the embedded fields.  It then changes the syntax to:

<span style="background-color:#101010">referenced field value</span>

Meanwhile the form also collects the plain text from the XHTML and determines the start/end offsets of the field data.  When the user types into the field, we use the change event to ensure that their edits are within the range of one of the referenced fields.  If the edit is not in range, the form ‘swallows’ the event.

If the field has no value, we replace the field content in the display with a string of non-breaking spaces.

When exiting the field, we gather all the ranges of text that correspond to fields and push them back out to the field objects.

It’s all fairly simple in concept, but the code ended up being fairly complex.  One of the hardest parts was handling undo/redo.  Undo and redo will modify a field without firing a change event. This means that the cached offsets become stale.  There is code that detects if the field value has been modified since the last change event.  If so, the offsets are re-calculated.

Some Possible Enhancements

It would be nice if we could limit keystrokes to what is allowed by the referenced field.  e.g. if the referenced field is numeric, allow only numeric text to be entered for that floating field

Currently if the floating fields are updated externally from the floating field editor, the changes are not detected.

We could update the referenced fields on every keystroke rather than only on exit.

The sample does not handle xfa:embedMode="formatted". If this were supported then when editing the values you would see the edit format of the field and when you exit you would see the formatted value.

Working with Multiple Data Records

The XFA processor has a notion of data records.  A record is the data that populates one instance of your form.  If you are designing interactive forms, you have probably never had more than one data record in your form.  The notion of multiple records is primarily for processing large print jobs in LiveCycle Print.  Consider the case where you want to print customer statements.  You extract an XML file from your database with the data for all 2000 customers.  Then in a single call to LiveCycle Print you print all 2000 records.

I won’t go into all the details of setting up a multi-record print.  My intention today is to show how you can leverage multiple record support in interactive forms.

Data Record

Suppose you have a form that binds to a root data node named: "form1".  When there is a single data record, your data looks like this:

<xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
   <xfa:data
>
      <form1>…</form1>
   </xfa:data>
</xfa:datasets>

In the simple case, a form with three records will look like:

<xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
   <xfa:data>
      <form1>…</form1>
      <form1>…</form1>
      <form1>…</form1>
   </xfa:data>
</xfa:datasets>

But by default Acrobat/Reader will display only the first record.  But with a few simple script commands you can navigate to different records as well as add and remove records.

Script Expressions

The Current Record
The current data record is easy to find.  We have set up a convenience property: xfa.record.

Record Count
xfa.record.parent.nodes.length;

Current Record Number
xfa.record.index;

Record Count
xfa.record.parent.nodes.length;

Strictly speaking, this will not always return the record count… but that’s in the deep end.

Add a Record
var newRecord = xfa.datasets.createNode("dataGroup", xfa.record.name);
xfa.record.parent.nodes.append(newRecord);

Remove a Record
var vRecordToRemove = xfa.record.parent.nodes.item(xfa.record.index);
xfa.record.parent.nodes.remove(vRecordToRemove);

Goto a Record
xfa.datasets.dataWindow.gotoRecord(nRecord);

Sample

I have attached a sample that exercises all these script commands.

Exporting XML data

One thing you will notice when you export or submit data as XML, the result will change depending on if there is one or more than one record included.  From the example above, if I export as XML and there’s one record I’ll get:

<form1> … </form1>

If I export a data with 3 records, we need to add an aggregating element so that it remains valid XML:

<xfa:data xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
    <form1>…<form1>
    <form1>…<form1>
    <form1>…<form1>
</xfa:data>

Changing the Current Record

You need to be aware that when you call dataWindow.gotoRecord(), the XFA processor will perform a re-merge with the new data.  Doing a remerge means that you lose any changes you’ve made to your form objects.  e.g. you will lose field highlighting, choice list definitions populated by script etc.

The Deep End

There are some advanced options for handling very large datasets on the server.  If you think about it, a large print job could contain many thousands of records, and the input could be a data file that is many gigabytes.  To handle that case there are options in configuration that allow you to process the file incrementally.  Part of that involves specifying a record element by level or by name.  e.g. you could say that the name of the record node is "customer".  In this sample XML:

<customerRecords>
  <jobDetails>…</jobDetails>
  <customer> … </customer>
  <customer> … </customer>
  <customer> … </customer>
  <customer> … </customer>
</customerRecords>

there are 4 records, but the <customer> parent element has 5 children.  That’s why, strictly speaking, the expressions: xfa.record.index; and xfa.record.parent.nodes.length; are not always reliable.

But if you haven’t set the config file options, then there’s no problem.

Data Window

To round out the story of data records, we need to say a bit more about incremental processing — commonly referred to as "lazy loading".  The idea is that the XFA processor holds only a small number of data records in memory.  As the XFA processor loads the data file it progressively loads data records into a window and when they’ve been processed they are discarded.  The window can specify how many records before and after the current record are kept in memory.  In this way we can process a multi-gigabyte data file without loading it all into memory.

As mentioned, there are configuration options for setting up the data window behaviour as well as a dataWindow scripting object.  But these details would need to be the topic of another blog entry.

XFA Scripting API Reference

Some time ago we had an intern build a flex application that gave internal people a nice reference for the XFA scripting object model.  Our nightly build process generates XML files that describe all the objects, properties and methods available within XFA scripting.  This flex application loads those files and gives a very convenient tree view where we can drill into the details of the object model.  Any time the build is broken and this application isn’t available, I suffer withdrawal symptoms.  So I figured maybe you would find it useful as well.  I have taken the flex application, changed it up so that it works stand-alone and I’ve embedded it in a PDF.  The result is ScriptReference.pdf. (Warning: I encountered some strangeness when I tried to launch this pdf directly from firefox.  Probably best to right-click/download and then launch.)

ScriptReference.pdf

The reference divides up the objects among the various top-level models in XFA (form, template, data, etc).  The way you use it is that you select the object you are interested in — e.g. the field object under the form category — and then the right hand side of the page will display all the properties and methods available. 

Notice that the reference exposes some of our object hierarchy — each object has a parent class.  The display shows only the properties and methods for the current object.  If you want to see the properties and objects inherited from the parent class, then click on the parent object class.

Under the "XFA" category, you’ll find a miscellaneous list of objects, including the classes that occur higher in the class hierarchy.

The set of descriptors needs a bit of explanation.  For Properties:

type: The type of content (String, Object, Bool, integer etc).  Some properties may say "Multiple" because the property type changes depending on the context.  e.g. field.rawValue returns a string for text fields, but a number for numeric fields.

Based on schema: yes/no: indicates whether this property is exposed as a result of being part of the XFA schema.  i.e. part of the file format.  This value will be "no" for properties such as field.fillColor (a shortcut property) or field.formattedValue (a calculated value).

access: GET or SET or both. i.e. whether this property is read-only or if it is read-write.  Note that objects in the template may tell you that they are ‘set’able, but if you try to set them in Acrobat you’ll get an error.  This is because the template is read-only in Acrobat — but is read/write in other contexts — specifically on the server.

version: The XFA version where this property was introduced.

For methods, the form shows the method signature, displays the version number and describes the parameters.

Search

At the bottom left you will see a couple tabs where you can enter a search string.  When searching by name, the tree list will get filtered to those objects/properties that match the given name.  When searching by version, the tree list gets filtered to those objects/properties that match the specified version.

Warning: developer-speak

The descriptions of the objects, methods and properties are exactly as they were written by the developers who implemented the functionality.  What you probably already know about developers is that we not always good grammar writing.  The documentation you get with Designer has been produced by our talented bilingual tech writers who can translate from developer-speak to English.  So take the descriptions with a grain of salt, and if you find them confusing, consult the real documentation.

Check for New Version

Notice the "Check for New Version" button.  When we release a new version of XFA inside Acrobat/Reader/LiveCycle I will need to produce an update.  Or maybe I’ll fix a bug.  Periodically you should click the button and find out if the copy you have is the latest.

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
2:XFA:form1[0]:NumericField1[0]:calculate

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() { … }; 

this.getArea=getArea;
this.width=width;
this.height=height;

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

Naming

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:

form1.foo.foo() is not a function
1:XFA:form1[0]:NumericField3[0]:calculate

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:

Rectangle.setH(4);
Rectangle.setW(5);
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.

Base64 Encode a PDF attachment

This blog entry has been re-published with updated information.

Some time ago I experimented with PDF attachments — trying to add them to my XML data.  I wasn’t happy with the outcome at that time, and I was going to leave it.  But then I saw a customer scenario that called for this capability, and then one of my regular commenters (Bruce) brought up the topic as well.  So I’ve tried again and had a little more success this time. 

The end goal is to copy a PDF attachments into XML data.  If you can do so, it opens up a couple of interesting possibilities:

  • Take any attachments a user has added to the PDF and include it in a form submission or in a web service request
  • Take image attachments and use them to populate image fields

Acrobat methods for attachments

The acrobat document object has a property: dataObjects that returns an array of all the attachments in the current document.  Then are a set of methods for dealing with attachments: openDataObject, getDataObject, createDataObject, importDataObject, removeDataObject, getDataObjectContents, and setDataObjectContents.

The interesting method in our case is getDataObjectContents().  It returns a stream object with the contents of an attachment.  If your attachment happens to be textual, then you can use util.stringFromStream() to convert to a string value:

var inputStream = event.target.getDataObjectContents("MyNotes.txt");
Notes.rawValue = util.stringFromStream(inputStream);

The default encoding for binary attachments is a hex-encoding (each byte written as a 2 digit hex value).  However, when your attachment is in a binary format, the standard way to include it in an XML file is to encode it as base64.  To convert to a base64 encoding, use the Acrobat Net.streamEncode() method:

// Get a stream for the image attachment
var inputStream = event.target.getDataObjectContents("Smile.jpg");

// Get a new stream with the image encoded as base64
var vEncodedStream = Net.streamEncode(inputStream, "base64");

// Get a string from the stream
var sBase64 = util.stringFromStream(vEncodedStream);

// assign the base64 encoded value to an image field
sampleImage.rawValue = sBase64;

We know there are issues with Net.streamEncode() failing where content has null bytes.  However, when used in the context of encoding an attachment, it seems to work fine.

When I first looked at this problem I assumed that the Net.StreamEncode() method wasn’t working so I wrote a base64 encoding JavaScript.  It works fine — but it is slow!  On a 140K image, it takes 10 seconds to encode.  I’ve included this code in the sample just for interest sake.

Conversion to Base64

The attached sample form has an initialization script that displays a subform for each attachment.  There are two buttons that will take the corresponding attachment, convert it to base64 and assign it to the image field value.  One button uses the (slow) JavaScript encoding algorithm the other button uses Net.streamEncode() and works pretty quickly. 

The easiest and most reliable way to encode attachments in your XML data is to keep them in a hex encoding.  But of course, for this to work the consumer of your XML needs to be able to handle hex encoding as well.

Updated Message Handler and Debug Fragments

As I mentioned, I need to update a number of previously distributed script objects as fragments.  The reasons for the updates are:

  1. Make sure the code passes the lint check
  2. Add version information so that we can check whether your copy of the fragment/sample is stale or not
  3. New functionality/bug fixes

Message Handling

This entry discussed handling exception objects in a JavaScript catch block.

Here is the updated fragment.  You ought to be able to simply copy it into your fragment library.

The updates to the script include:

Fixed line end handling in Designer

Scripts marked runAt="server" will run when you save a form in Designer (this is explained here).  Any log messages issued by those scripts are emitted to the log tab in Designer.  For line breaks to appear correctly in this window, the line endings need to include both a carriage return and line feed.  The getServerMessage() method now makes sure to format the message accordingly.

Handle String Exceptions

The message handler functions can now handle the case where the code throws a string.

To see how this is useful, suppose you’re writing code for a click event.  Part way through your 200 lines of code you encounter a condition where you want to give an error message and exit.  But you can’t.  You’re not in the context of a function call, so you can’t simply say "return;" and get out.  You end up putting a big conditional branch in your code:

try {
    if (errorCondition) {
        xfa.host.messageBox("This event failed");
    } else {
        … 150 lines of remaining code …
    }
} catch(err) {
    xfa.host.messageBox( scMessage.handleError(err) );
}

The alternative now is to simply throw a message string and get out.  Your revised event code looks like:

try {
    if (errorCondition) {
        throw "This event failed";
    }
    … 150 lines of remaining code …
} catch(err) {
    xfa.host.messageBox( scMessage.handleError(err) );
}

Debug Trace

This entry showed how to easily add debug trace information to your JavaScript functions.  The trace() method has been updated to include handling for function parameters with null values.

Here is the updated fragment.

Dependency Tracking

One of the really cool aspects of XFA forms is the dependency tracking mechanism.  Dependency tracking is the feature where your field (and subform) calculations and validation re-fire whenever any of their dependents change.  Today I’ll explain a bit about the mechanics of how dependency tracking is implemented as well as have a look at possible design issues related to this mechanism.

Simple Example

Suppose my purchase order form has order rows with fields: price, quantity and subtotal along with a total field.

The subtotal field has a JavaScript calculation: "price.rawValue * quantity.rawValue"

The total field has a FormCalc calculation: "sum(order[*].subtotal])"

Now whenever price or quantity fields change, the corresponding subtotal field will be recalculated automatically.
Whenever any of the subtotal field values change, the total calculation will re-fire.

Discovering Dependencies

When the form is first opened, all the calculations and validations are executed.  While executing a calculation or validation, the XFA engine keeps track of each object (field, subform, data node) that gets referenced during the script execution.  Each referenced object is added to its dependency list.  In our example, each subtotal field becomes dependent on the price and quantity fields from its row.  The total field becomes dependent on all the order subforms and on all the subtotal fields.  The mechanism is robust enough that even if you reference your objects indirectly, we still find the dependency.  e.g. the subtotal calculation could be written as:

parent.nodes.item(0).rawValue + parent.nodes.item(1).rawValue

Since the nodes we visited were still price and quantity, the dependencies are established just the same.

In some cases, the dependency list will grow as values change.  Consider this calculation:

if (A.rawValue < 100) {
    A.rawValue;
} else {
    B.rawValue;
}

Suppose the first time it executes, field A has a value of 20.  This means the code will not execute the else clause and will not access field B.  The initial dependency list will include only A.  However, if the A of changes to 200, the calculation will re-fire; the else clause will be evaluated and field B will be added to the dependency list.

If your calculation or validation calls a script object, dependencies will continue to be tracked during the execution of the script object method.

Dependent Properties

What constitutes a field change? What changes cause a calculation to re-fire?  I don’t have a complete list.  But changing a field’s visual properties (colours, captions, presence) do not cause a dependent to recalculate.  Changing the value or changing the items in a list box will cause a dependent calculation/validation to re-fire.

Turn off Automatic Calculations

If, for some reason, you’re not happy with the default dependency tracking, then you can turn it off. There are two switches to control calculations and validations independently:

xfa.host.calculationsEnabled = false;
xfa.host.validationsEnabled = false;

Note that turning validationsEnabled off disables not only script validations, but also turns off mandatory field and picture clause validations.

Dependency Loops

It is possible to get into a situation where sequence of dependency changes goes into an infinite loop. In these cases, the XFA engine will allow the calculation or validation to re-fire a fixed number of times (10) and then will quietly stop re-firing the calculation or validation until the next time a dependent changes.  While the 10 times limit is ok in most cases, I have seen forms where this has been the root of performance issues.  If you have a dependency loop and your calculations are re-firing, you want to be aware of it and you need to fix it. 

Dependency loops are typically introduced when a calculation or validation modifies another field.

I should highlight that statement.  If your calculation or validation minds its own business and never modifies another field you shouldn’t run into any loops.  There are lots of cases where a calculation or validation modifies another field and everything works fine — but tread carefully.

Looping example

In order to track when my validations were changing, I wrote validation scripts write to a debug field:

field T1 validation:

debugTrace.rawValue += this.rawValue;
true;

field T2 validation:

debugTrace.rawValue += this.rawValue;
true;

Both T1 and T2 now have a dependency on field: debugTrace

The sequence of events:

  1. Field T1 changes
  2. T1 validation fires and modifies debugTrace
  3. T2 validation fires and modifies debugTrace
  4. T1 validation fires and modifies debugTrace
  5. T2 validation fires and modifies debugTrace

Here is a sample form to illustrate this example.

Note that if we remove the validation from T2, the circularity stops.  The validation on T1 modifies debugTrace, but after the validation completes, debugTrace does not change and T1’s validation does not re-fire.