Archive for October, 2009

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.