Expand my Community achievements bar.

Dive into Adobe Summit 2024! Explore curated list of AEM sessions & labs, register, connect with experts, ask questions, engage, and share insights. Don't miss the excitement.
SOLVED

Searchable drop down menu?

Avatar

Level 2

I have several drop down lists in a PDF I am creating. These drop down lists contain diagnoses to be used by physicians. With there being a LOT of diagnoses, it would be extremely convenient if I had a "search" function of some kind that could be utilized as the wording of the descriptions for the codes/diagnoses can vary: (Ex. "Rib Pain" or "Pain in Rib")

As of now, the only option is to select the first letter of the name they believe the diagnosis to have. Using this example, they would select the drop down menu and press "R" and the list would jump down to the beginning of the R's. But, if the name is actually "Pain in Rib", they would have to search through all the other diagnosis codes to locate it.

I believe a search function would be the most appropriate for something like this. But, I am having some trouble locating any info on coding for something of this nature. If there are any suggestions that could make this, (or something that accomplishes the same task), possible, I am all ears.

Thanks!

I included a pdf with the fragment with the drop down menus as well as a list of the diagnoses I have in them.

I appreciate any help or ideas. I've never been let down by the people here in these forums.

1 Accepted Solution

Avatar

Correct answer by
Level 8

I was able to use the Net.HTTP and SOAP object to access the same DMTI service, but it worked only on the console window:

var jData;

var prm =

{cVerb:"GET",

aHeaders: [{name: "Authorization", value:"authentication-token"}],

cURL: "https://app2.dmtispatialxxx.com/servicesxxx/rest/suggest?limit=60&includePostalCode=true&address=20%...",

oHandler: {

   response: function (msg, uri, e) {

    console.println("\nThis is the response method:");

    console.println("msg=" + msg);

    //for (var p in msg) {

    //  console.println("p=" + p);

    //  console.println("msg[p]=" + msg[p]);

    //}

    var data = SOAP.stringFromStream(msg);

    jData = JSON.parse(data);

    console.println("Length of json data: " + jData.length);

    console.println("data=" + data);

    console.println("e=" + e);

   }

}

};

Net.HTTP.request(prm);

Response from the console window:

This is the response method:

msg=[object Stream]

Length of json data: 5

data=[{"matchCount":1,"suggestion":"20 CROWN STEEL DR MARKHAM ON L3R9X9","aliasSuggest":null},{"matchCount":1,"suggestion":"20 CROWN ST QUINTE WEST ON K8V1N6","aliasSuggest":null},{"matchCount":1,"suggestion":"20 CROWN ST ST. CATHARINES ON L2M3L3","aliasSuggest":null},{"matchCount":1,"suggestion":"20 CROWN ST THUNDER BAY ON P7B3J6","aliasSuggest":null},{"matchCount":1,"suggestion":"20 RUE CROWN DANVILLE QC J0A1A0","aliasSuggest":null}]

e=undefined

Unfortunately, the above method didn't work from a the click event of a button (in Adobe LiveCycle form). I got this error:

NotAllowedError: Security settings prevent access to this property or method.

Net.HTTP.request:19:XFA:form1[0]:subform[2]:Button4[0]:click

It looks like the only option now is to develop a wrapper component that can be invoked using the simple URL and to include the authentication token in the query parameters. Also, there is another problem... I have to get authentication code every time it is expired. Currently, there is Java code responsible for this part.

Question:

Why not develop a simple javascript program that can be invoked using HTML or jsp code, and return the result by clearing the output and just dump the result from the xhr response object? While I can develop the server component, but it seems a lot more easier to develop such component using only HTML and javascript. But, I am not  sure if it can be done, I just think it is possible.

View solution in original post

66 Replies

Avatar

Level 2

Great information!  Thank you for responding to me and putting together the recipe.  This really adds value to my form.

Avatar

Level 8

The link provided is not working.

Tarek

Avatar

Level 8

Thanks a lot BR001_ACP !

I need some help to customize the parts developed for the Drop-Down List with search feature.

I am trying to develop a reusable fragment to be applied for tens of drop-down list fields (DDL).

Basically, I will pass the DDL field to the fragment, and the modified fragment (the parts you developed) will copy the items from the DDL to the List Box inside the fragment, in addition to using the fragment as an overlay on top of the passed DDL parameter field. Finally, when a value is selected, it will be passed back to the original DDL field.

For now, (see code below) I want the most efficient way to copy the items "copyItems()" from the DDL field "theDDLField" to the search result list box "theResultsField".

See the developed fragment here.

Also, see snapshots to clarify.

Appreciate your help.

var theDDLField=null;

var theSearchField=null;

var theResultsField=null;

function clearVars() {

  theDDLField=null;

  theSearchField=null;

  theResultsField=null;

}

function setMatchFirstCharacters(prmMatch) {

  this.MatchFirstCharacters.value = prmMatch;

}

function setVars(prmField) {

  theDDLField = prmField;

  theSearchField = Background.Search;

  theResultsField=Results.ListBox;

}

function copyItems() {

  //copy items from the DDL Field (theDDLField) to the List Box (theResultsField)

}

function show(prmDDLField) {

  setVars(prmDDLField);

  this.presence  = "";

  this.relevant = "-print";

  this.x = prmDDLField.x;

  this.y = prmDDLField.y;

  theSearchField.w = prmDDLField.w;

  theSearchField.caption.reserve = prmDDLField.caption.reserve;

  theDDLField.presence = "hidden";

  copyItems();

  xfa.host.setFocus(theSearchField);

}

function hide() {

  this.presence = "hidden";

  if (theDDLField) {

  theDDLField.presence = "";

  theDDLField.execEvent("exit");

  } else {

  app.alert("Error: theDDLField is null while in hide().");

  }

  clearVars();

}

923384_pastedImage_13.png

923385_pastedImage_0.png

Avatar

Level 10

Hi Tarek,

You can use something like;

function copyItems(dropDownList, listBox) {

    var itemList = [];

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

        itemList.push(dropDownList.getDisplayItem(i));

        itemList.push(dropDownList.getSaveItem(i));

    }

    listBox.setItems(itemList.join(),2)

}

Then call it with;

copyItems(theDDLField,theResultsField);

Or if you don't have a bound item, just

function copyItems(dropDownList, listBox) {

    var itemList = [];

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

        itemList.push(dropDownList.getDisplayItem(i));

    }

    listBox.setItems(itemList.join())

}

That is without the getSaveItem and without the ",2" on the join.

Another possible complication is if either your display item or the bound item could have a comma character in it then you will need to escape it, as the setItems takes a comma separated list, so;

        itemList.push(dropDownList.getDisplayItem(i)).replace(/,/g, "\\,");

Regards

Bruce

Avatar

Level 8

Thanks a lot BR001_ACP​.

I am now nearly done with the fragment, still facing some UI and usability issues.

Appreciate it if you could follow me to the related forum post here:

Using fragment to implement Drop-Down List Field with search / auto populate

Tarek

Avatar

Level 2

Is there any way to combinate this auto populated drop with a web service (REST or SOAP)?

Could be very useful for a user search integration within a LDAP directory.

Avatar

Level 10

Hi,

I have not had a need to try this, though you see this more and more.  Do you have a public web service available that I integrate my "searchable drop down" control with?

Regards

Bruce

Avatar

Level 2

Hi Bruce,

I would suggest this web service http://www.webservicex.net/country.asmx?WSDL  (Download the XML, save it as *.wsdl file and create a new data connection within Designer with the saved *.wsdl).

The web service handles over a XML based list of all countries.

Regards
Moonraker

Avatar

Level 10

This webserive does not seem to offer a partial match search, I assume you want people to be able to type something like "new" and get all the countries that contain "new" in their name? 

Still I can see this being useful, I might try the google places API, Google Places API  |  Google Developers , and see if I can get that to work, but this is a REST service not a SOAP one.

Will let you know,

Bruce

Avatar

Level 10

Hi,

Here is a sample using the Google Places web service, seem to work ok once you do the "trust document" thing on the yellow message bar that is displayed at the top of the form.

https://sites.google.com/site/livecycledesignercookbooks/home/GooglePlaces.pdf?attredirects=0&d=1

You will need to get a Google Places API key which you can get at Get a key for Google Places API Web Service  |  Google Places API Web Service  |  Google Developers

Bruce

Avatar

Level 8

BR001​ Thank you so much! We have migrated most of our forms from Adobe LiveCycle PDF to HTML5, and implemented autocomplete feature for address lookup.

The other day I heard some business users asking for this feature (autocomplete) to be implemented on some legacy PDF forms. I think your sample will be of a great help.

Thank you!

Tarek

Avatar

Level 2

Thank you very much BR001​ for the very helpful example.

Would it be possible to change the response format of the Google Places API to XML and parse the XML response into the XFA form?

Avatar

Level 10

Hi,

Getting an XML response would mean changing url bit from

    https://maps.googleapis.com/maps/api/place/autocomplete/json

to

    https://maps.googleapis.com/maps/api/place/autocomplete/xml

And then using the usual resolveNode methods;

if (googlePlacesResult !== "") { // probably not a trusted document yet

  var AutocompletionResponse = XMLData.parse( googlePlacesResult, true )

  if (AutocompletionResponse.status.value === "OK") {

   var predictions = AutocompletionResponse.resolveNodes("prediction[*]")

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

    var prediction = predictions.item(i);

    list.push([prediction.resolveNode("description").value.replace(/,/g, "\\,"), prediction.resolveNode("place_id").value]);

   }

  }

}

Then I guess you would have to make similar changes to the listbox click event

Bruce

Avatar

Level 8

Hi BR001​,

Could you please take a look at this post:

Re: Using fragment to implement Drop-Down List Field with search / auto populate

I was able to reimplement your code in the form to perform autocomplete based on Google Places API. Now I need to use another service based on DMTI not Google Maps. The problem is that I have to authenticate using xhr.setRequestHeader() function before I can invoke the URL. How I can do that?

Appreciate your help.

Tarek

Avatar

Level 10

Hi Tarek,

There is the Net.HTTP object in the Acrobat JS reference, https://wwwimages2.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/AcrobatDC_js_api_reference.pdf?...

But I haven't used it and believe there's some security issues to resolve.

You can't update the headers using the FormCalc GET function I used.

Maybe you can write a service on your server for the PDF form to call and pass to DMTI?

Bruce

Avatar

Correct answer by
Level 8

I was able to use the Net.HTTP and SOAP object to access the same DMTI service, but it worked only on the console window:

var jData;

var prm =

{cVerb:"GET",

aHeaders: [{name: "Authorization", value:"authentication-token"}],

cURL: "https://app2.dmtispatialxxx.com/servicesxxx/rest/suggest?limit=60&includePostalCode=true&address=20%...",

oHandler: {

   response: function (msg, uri, e) {

    console.println("\nThis is the response method:");

    console.println("msg=" + msg);

    //for (var p in msg) {

    //  console.println("p=" + p);

    //  console.println("msg[p]=" + msg[p]);

    //}

    var data = SOAP.stringFromStream(msg);

    jData = JSON.parse(data);

    console.println("Length of json data: " + jData.length);

    console.println("data=" + data);

    console.println("e=" + e);

   }

}

};

Net.HTTP.request(prm);

Response from the console window:

This is the response method:

msg=[object Stream]

Length of json data: 5

data=[{"matchCount":1,"suggestion":"20 CROWN STEEL DR MARKHAM ON L3R9X9","aliasSuggest":null},{"matchCount":1,"suggestion":"20 CROWN ST QUINTE WEST ON K8V1N6","aliasSuggest":null},{"matchCount":1,"suggestion":"20 CROWN ST ST. CATHARINES ON L2M3L3","aliasSuggest":null},{"matchCount":1,"suggestion":"20 CROWN ST THUNDER BAY ON P7B3J6","aliasSuggest":null},{"matchCount":1,"suggestion":"20 RUE CROWN DANVILLE QC J0A1A0","aliasSuggest":null}]

e=undefined

Unfortunately, the above method didn't work from a the click event of a button (in Adobe LiveCycle form). I got this error:

NotAllowedError: Security settings prevent access to this property or method.

Net.HTTP.request:19:XFA:form1[0]:subform[2]:Button4[0]:click

It looks like the only option now is to develop a wrapper component that can be invoked using the simple URL and to include the authentication token in the query parameters. Also, there is another problem... I have to get authentication code every time it is expired. Currently, there is Java code responsible for this part.

Question:

Why not develop a simple javascript program that can be invoked using HTML or jsp code, and return the result by clearing the output and just dump the result from the xhr response object? While I can develop the server component, but it seems a lot more easier to develop such component using only HTML and javascript. But, I am not  sure if it can be done, I just think it is possible.

Avatar

Level 8

Thanks BR001​...

I managed to develop a wrapper component using Java Servlet that will: 1) get authentication token then call suggest DMTI service (search as you type) and 2) get the token then call recognize DMTI service (get address parts). Also, I implemented the Servlet call from PDF using "FromCalc.Function.Get(url)" and it's honestly beautiful.

Not only it's working fine, in addition, now I have a reusable component that can be implemented on any Text field.

I have the following questions to help make the implementation more efficient (assume the component name is "AddressAC"):

- Since I have to plugin the function calls (to the various related events of AddressAC) to the Search Field (to apply Autocomplete), is there a way to carry out this step with less effort to reduce errors? I remember that there is Prototype object or something like that, but I can't remember the exact details. See snapshots below to clarify

- Is there a way to include the component "AddressAC" only once as a fragment, say on the master page, and then reuse it on any subform? For now, I have to include the fragment in every subform to make it work. I think the problem if we use fragment form the Master Page, it will cause errors in x-y coordinates (relative vs. absolute).

SearchField1.PNG

SearchField2.PNG

SearchField5.PNG

SearchField3.PNG

SearchField4.PNG

Avatar

Level 10

Good to see you got it working, you must have some of the most complicated PDF forms out there.

I did use the proto objects on a version of the 'ghost text', Adobe LiveCycle Designer Cookbooks by BR001: Adding Ghost Text to LiveCycle Designer form , Be careful to read Radzmar's comment, I would probably use his approach now.  The way styles was introduced in ES3 could caused some conflict.

It requires a bit of editing in the XML Source but you can add your AddressAC as a "Reference Object", just drag it in the Hierarchy view to the structure tree node called "(Reference Objects)".  Then in the XML Source give it an id, so will probably look something like;

<subform w="203.2mm" layout="tb" name="AddressAC" id="AddressAC ">

Then you can turn any subform into a AddressAC object by adding a use attribute, something like;

<subform w="203.2mm" layout="tb" name="AddressAC2" use="$template.#subform.AddressAC">

To save editing the XML for every reference you could create one and then make a custom object out of it.

Regards

Bruce

Avatar

Level 8

Thanks again.

I checked the proto object, and it is now clear to me how to use it.

Just to verify my understanding, in order to use the proto object, first you have to create it, then use it with the "use" attribute. But, in order to create the proto object, you must add the <proto> tag using XML source view, since you cannot use the UI to insert the proto object, correct?

What about including the fragment in the master page (which has the Autocomplete with the pull-down effect), and use it in all other subform? Do you think this will work?

Tarek