docassemble icon indicating copy to clipboard operation
docassemble copied to clipboard

Ajax calls for combobox not working

Open cschwarz007 opened this issue 3 years ago • 3 comments

I am trying to fill a dropdown from server responses and noted that input type: ajax does not do in my interview as I expected (I am on v1.3.50 currently).

I noticed also in the documentation it does not work - is something broken with this functionality?

cschwarz007 avatar Aug 19 '22 11:08 cschwarz007

By default, the Ajax input requires you to type at least 4 characters. You can adjust that by setting trigger at: 2 (the smallest trigger).

It works fine in the documentation link you shared if you type at least 4 characters.

nonprofittechy avatar Aug 19 '22 13:08 nonprofittechy

Then I might need some help to find the suitable way please - here is what I am trying to do: I have dropdown that I want to populate with data from a server (API). The dropdown actually is a list of ~100 company names from which the user should be able to pick one.

However, response time is rather high (approx. 5s). Hence I would want the question to show with the dropdown being populated (via ajax) after loading the screen...

As input type: ajax requires at least 2 characters to be typed, it doesn't help here...

This is roughly what the question currently looks like, but given the synchronous call to the API function it takes a while to load...

---
question: |
  Select affiliate  
subquestion: |
  Select an Affiliate from the dropdown below.
fields:
  - Affiliate ID: company.id
    code: |
     server_getaffiliates() 
     #This actually is a call to the API (py module) which gets the data from the server

cschwarz007 avatar Aug 19 '22 19:08 cschwarz007

The JavaScript behind datatype: ajax has some problems, so I'll need to work on that. It works sometimes.

Anyway, datatype: ajax is designed for long lists, not for lists that take a long time to obtain.

To load the options of a dropdown after the screen has loaded, you could use action_call() along with some jQuery:

mandatory: True
question: |
  Your favorite fruit is ${ favorite_fruit }.
---
question: What is your favorite fruit?
fields:
  - Fruit: favorite_fruit
    choices:
      - Please wait
script: |
  <script>
    action_call('fetch', {}, function(data){
      var elem = getField('favorite_fruit');
      $(elem).empty();
      for (var i = 0; i < data.length; ++i){
        $(elem).append($("<option>").val(data[i]).text(data[i]));
      }
    })
  </script>
---
event: fetch
code: |
  # Your code that calls the slow API would go here.
  json_response(["Apple", "Orange", "Peach", "Pear"])

Another way you could handle this problem is by keeping an in-memory cache of the list of companies in Redis. You could do something like this:

modules:
  - .loadlist
---
mandatory: True
code: |
  cache_refreshed
  welcome_screen
  final_screen
---
code: |
  background_action('bg_cache_refresh')
  cache_refreshed = True
---
event: bg_cache_refresh
code: |
  # ensure that the list stored in the Redis cache is current
  get_fruit_list(refresh=True)
  # return from the background task
  background_response()
  # nothing is returned because the purpose was just to put
  # some data in Redis, to be retrieved later
---
question: |
  Welcome
subquestion: |
  I am just stalling for time because there is an API call running in the
  background.
continue button field: welcome_screen
---
question: What is your favorite fruit?
fields:
  - Fruit: favorite_fruit
    code: get_fruit_list()
---
event: final_screen
question: |
  Your favorite fruit is ${ favorite_fruit }.

where loadlist.py is:

from docassemble.base.util import DARedis
# Using the time package is just for purposes of simulating 
# a long-running API call
import time

__all__ = ['get_fruit_list']

def get_fruit_list(refresh=False):
  r = DARedis()
  fruitlist = None if refresh else r.get_data('fruitlist')
  # if the list is not in the cache, or the cache should be 
  # refreshed, call the API
  if fruitlist is None:
    # fruitlist would actually set by an API call; this is just
    # a demonstration
    fruitlist = ["Apple", "Orange", "Peach", "Pear"]
    # simulate a slow API call
    time.sleep(5)
    # store the list in a cache for five minutes
    r.set_data('fruitlist', fruitlist, expire=60*5)
  return fruitlist

In this interview, a background task is launched in advance, before the user sees the screen with the list of companies (fruit in this example). This background task will fetch the list of companies from the API and store the results in Redis. Then when the user gets to the screen with the list of the companies, the function get_fruit_list() will return the list of companies from the cache, with no five-second delay.

The list will be stored in the Redis cache for no more than five minutes. If the user takes more than five minutes to get to the screen with the list of companies, the list will need to be fetched by the web application and the user will need to look at a spinner, but that is probably unlikely to happen.

jhpyle avatar Aug 20 '22 01:08 jhpyle