docassemble icon indicating copy to clipboard operation
docassemble copied to clipboard

API call server-side with review

Open cschwarz007 opened this issue 3 years ago • 1 comments

I am trying to run the following scenario: As part of an interview, I am collecting contract parties with name and address. Any contract party within our company, I have a central system which can deliver that information and is accessible via API. Hence, I would like to look-up name and address accordingly (each company has a unique ID that is used for lookup), but to it server-side (due to security reasons). But I need the user to review the information the system delivers as address and/or name may not always be entirely correct and may require user correction.

The below is my current code which works well, except for the fact that I did not find a way to present the derived information (company name and address) to the user for review. Ideally, the screen will show the question(s) relating to name and address with filled-in answers (like the review functionality) right after clicking the "Lookup" button (i. e. prior to clicking "continue").

Can someone help me find the "right" way?

objects:
  - company: |
      DAList.using(
        object_type=Person,
        there_are_any=True,
        complete_attribute='complete')
---
code: |
  company[i].name.text
  company[i].address.address
  company[i].address.zip
  company[i].address.city
  company[i].address.country
  company[i].complete = True
---
question: |
  What is the address of the
  ${ ordinal(i) } location?
subquestion: |
  <span id="resultsArea"></span>
fields:
  - Affiliate ID: companyID
    datatype: integer
    required: False
  - html: |
      <button id="run" class="btn btn-primary">Lookup</button>
  - Company name: company[i].name.text
    default: Company Ltd.
  - Address: company[i].address.address
    default: Some street 1
    input type: area
    rows: 2
  - ZIP Code: company[i].address.zip
    default: 12345
    datatype: text
    required: False
  - City: company[i].address.city
    default: Berlin
  - Country: company[i].address.country
    code: |
      countries_list()
    default: DE
script: |
  <script>    
    $("#run").click(function(){
      var s = getField('companyID').value;
      console.log(s);
      action_call('myaction', {affiliateID: s, i: ${i}}, function(data){
        $("#resultsArea").html("The company name is " + data.result + ".")
      });
    });
  </script>
---
event: myaction
code: |
  affiliateID = action_argument('affiliateID')
  i = action_argument('i')
  # here comes a call to the API which gives a result
  result = "The result is company with number " + affiliateID
  company[i].complete = False
  company[i].name.text = result
  json_response(dict(result=result))
---
mandatory: True
question: |
  The companies are...
subquestion: |
  % for eachcompany in company:
  * ${ eachcompany.name.text }
  % endfor
---
question: |
  Would you like to add another company?
yesno: company.there_is_another

cschwarz007 avatar Aug 12 '22 08:08 cschwarz007

Here is the simplest way to do it, without any asynchronous calls from the web browser:

objects:
  - company: |
      DAList.using(
        object_type=Person,
        there_are_any=True,
        complete_attribute='complete')
---
code: |
  company[i].lookup_attempted
  company[i].name.text
  company[i].address.address
  company[i].address.zip
  company[i].address.city
  company[i].address.country
  company[i].reviewed
  company[i].complete = True
---
code: |
  # Call your API here
  if company[i].company_id == 42:
    company[i].name.text = 'Foobar Gmbh'
    company[i].address.address = '123 Main St.'
    company[i].address.city = 'Hamburg'
    company[i].address.country = 'DE'
    company[i].address.zip = '23423'
  company[i].lookup_attempted = True
---
question: |
  What is the affiliate ID of the company?
subquestion: |
  If you don't know it, just press Continue.
fields:
  - Affiliate ID: company[i].company_id
    datatype: integer
    required: False
---
question: |
  What is the address of the
  ${ ordinal(i) } location?
fields:
  - Company name: company[i].name.text
    default: Company Ltd.
  - Address: company[i].address.address
    default: Some street 1
    input type: area
    rows: 2
  - ZIP Code: company[i].address.zip
    default: 12345
    datatype: text
    required: False
  - City: company[i].address.city
    default: Berlin
  - Country: company[i].address.country
    code: |
      countries_list()
    default: DE
continue button field: company[i].reviewed
---
mandatory: True
question: |
  The companies are...
subquestion: |
  % for eachcompany in company:
  * ${ eachcompany.name.text }
  % endfor
---
question: |
  Would you like to add another company?
yesno: company.there_is_another

Note that I used a continue button field to ensure that the user sees the screen with the address on it, regardless of whether or not the information was automatically filled in by the API call.

Here is a way to do it that is more similar to what you had written:

objects:
  - company: |
      DAList.using(
        object_type=Person,
        there_are_any=True,
        complete_attribute='complete')
---
code: |
  company[i].name.text
  company[i].address.address
  company[i].address.zip
  company[i].address.city
  company[i].address.country
  company[i].reviewed
  company[i].complete = True
---
question: |
  What is the address of the
  ${ ordinal(i) } location?
subquestion: |
  <span id="resultsArea"></span>
fields:
  - Affiliate ID: company[i].company_id
    datatype: integer
    required: False
  - html: |
      <button id="run" class="btn btn-primary">Lookup</button>
  - Company name: company[i].name.text
    default: Company Ltd.
  - Address: company[i].address.address
    default: Some street 1
    input type: area
    rows: 2
  - ZIP Code: company[i].address.zip
    default: 12345
    datatype: text
    required: False
  - City: company[i].address.city
    default: Berlin
  - Country: company[i].address.country
    code: |
      countries_list()
    default: DE
continue button field: company[i].reviewed
script: |
  <script>    
    $("#run").click(function(e){
      var s = getField('company[i].company_id').value;
      if (s){
        console.log(s);
        action_call('myaction', {affiliateID: s, i: ${i}}, function(data){
          if (data.ok){
            for (let [key, value] of Object.entries(data.result)) {
              setField('company[i].' + key, value);
            }
          }
          $("#resultsArea").html("Got response: " + data.message);
        });
      }
      e.preventDefault();
      return false;
    });
  </script>
---
event: myaction
code: |
  indexno = int(action_argument('i'))
  # here comes a call to the API which gives a result
  company[indexno].company_id = int(action_argument('affiliateID'))
  if company[indexno].company_id == 42:
    json_response({'ok': True, 
                   'message': 
                   'Lookup successful', 
                   'result': {
                              'name.text': 'Foobar Gmbh',
                              'address.address': '123 Main St.',
                              'address.city': 'Hamburg',
                              'address.country': 'DE',
                              'address.zip': '23423'
                             }
                  })
  json_response({'ok': False, 'message': 'Could not find a company with that ID'})
---
mandatory: True
question: |
  The companies are...
subquestion: |
  % for eachcompany in company:
  * ${ eachcompany.name.text }
  % endfor
---
question: |
  Would you like to add another company?
yesno: company.there_is_another

Note that it is very important to run .preventDefault() on a <button> event, otherwise the web browser will synchronously submit the <form>.

Here is a way to do it with action_perform().

objects:
  - company: |
      DAList.using(
        object_type=Person,
        there_are_any=True,
        complete_attribute='complete')
---
code: |
  company[i].name.text
  company[i].address.address
  company[i].address.zip
  company[i].address.city
  company[i].address.country
  company[i].reviewed
  company[i].complete = True
---
question: |
  What is the address of the
  ${ ordinal(i) } location?
fields:
  - Affiliate ID: company[i].company_id
    datatype: integer
    required: False
  - html: |
      <button id="run" class="btn btn-primary">Lookup</button>
  - Company name: company[i].name.text
    default: Company Ltd.
  - Address: company[i].address.address
    default: Some street 1
    input type: area
    rows: 2
  - ZIP Code: company[i].address.zip
    default: 12345
    datatype: text
    required: False
  - City: company[i].address.city
    default: Berlin
  - Country: company[i].address.country
    code: |
      countries_list()
    default: DE
continue button field: company[i].reviewed
script: |
  <script>    
    $("#run").click(function(e){
      var s = getField('company[i].company_id').value;
      if (s){
        console.log(s);
        action_perform('myaction', {affiliateID: s, i: ${i}});
      }
      e.preventDefault();
      return false;
    });
  </script>
---
event: myaction
code: |
  indexno = int(action_argument('i'))
  # here comes a call to the API which gives a result
  company[indexno].company_id = int(action_argument('affiliateID'))
  if company[indexno].company_id == 42:
    company[indexno].name.text = 'Foobar Inc.'
    company[indexno].address.address = '123 Main St'
    company[indexno].address.city = 'Hamburg'
    company[indexno].address.zip = '02020'
    company[indexno].address.country = 'DE'
    log('Lookup successful', 'success')
  else:
    log('Could not find a company with that ID', 'info')
---
mandatory: True
question: |
  The companies are...
subquestion: |
  % for eachcompany in company:
  * ${ eachcompany.name.text }
  % endfor
---
question: |
  Would you like to add another company?
yesno: company.there_is_another

I don't think there is any one "right" way to do this.

jhpyle avatar Aug 12 '22 11:08 jhpyle

As always, 1000 thanks. This are 3 perfect solutions to my issue...

cschwarz007 avatar Aug 19 '22 11:08 cschwarz007