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;