1
0
-1

I want the autocomplete field to behave as if it were hooked up to a DDS, but draw suggestions from a data field that returns dynamic values instead (so that data field could be hooked up to an external web service via XHR).

The answer by Tim Stewart to this question suggests that this should be possible.

I've currently have a mockup solution as follows:

  1. Created and autocomplete field, a data field and set the autocomplete field to point to the data field.


  2. Created a Calculation rule on the data field, containing the following code:

    if (window.autocompleteField_previousValue !== data.autocompleteField)
    {
    window.autocompleteField_returnValue = [
    {"label":"Random 1: " + Math.floor(Math.random() * 20).toString(), "value":"random1"},
    {"label":"Random 2: " + Math.floor(Math.random() * 20).toString(), "value":"random2"},
    {"label":"Random 3: " + Math.floor(Math.random() * 20).toString(), "value":"random3"}
    ]
    console.log("New autoComplete returnValue:", window.autocompleteField_returnValue);
    window.autocompleteField_previousValue = data.autocompleteField;
    return window.autocompleteField_returnValue;
    }
    else
    return window.autocompleteField_returnValue;

    NOTE: The added if statement is to prevent this angular js error I encountered whereby a change in return value of a data field was causing re-evaluation of that data field (circular evaluation).

  3. Previewed the form with the Chrome F12 developer tools to observe the results.


The results I see in the F12 developer tools are as expected - a new value is returned only when a user has entered a new autocomplete field value.

The results I see in Maestro Preview are not as expected - the list of suggestions is displayed, but is not refreshed from the first time the data field was evaluated. That is, the suggestion list is the same in each preview session, no matter what I type in the autocomplete field.

Given that using native Avoka DDS functionality (particularly on the TM side) is not an option in this particular case, what is the best way of achieving what I've set out to do?  Is there a refresh function I can call?

Does the angular js error and the fact that Reference Data is only evaluated once by default mean that I'm pushing design boundaries? Is there a better way to do this?

    CommentAdd your comment...

    3 answers

    1.  
      1
      0
      -1

      I had no other option than to get this working.

      Ultimately in my situation, the browser itself needs to communicate with a web service directly because each call is user authenticated and results returned will vary depending on caller.

      The JavaScript below intercepts all XHR requests and if it's a DDS request where the service name is a URL, it substitutes the string <search> for the value typed in the autocomplete, makes that XHR request in stead and passes the result back to the DDS control as if it had come from Transaction Manager.

      The autocomplete field then works as usual, but communicating directly with a service rather than through TM.  Obviously, that service needs to be CORS enabled.

      Steps for usage are:

      1) Create an autocomplete field, leave 'Data Source' as default of 'TM Dynamic Data Service'.

      2) Plug the URL of an external data service into the DDS Configuration with <search> to be replaced with whatever is typed into the autocomplete.

      3) Create a data field anywhere on the form (I called mine 'ddsInterceptor') and paste the following code:


      DDS Interceptor
      if (window.ddsInterceptor !== true) {
        window.ddsInterceptor = true;
      
        (function(xhr) {
            var urlRegEx = /(\b(https?|ftp|file):\/\/[\-A-Z0-9+&@#\/%?=~_|!:,.;]*[\-A-Z0-9+&@#\/%=~_|])/i;
            var open = xhr.open;
            var send = xhr.send;
            xhr.open = function(method, url, async) {
                this.method = method;
                this.url    = url;
                return open.apply(this, arguments);
            }
            xhr.send = function(r) {
              realRequestUri = requestUriAsObject = undefined;
              try {  // Apply this intercept only when the DDS service name is present and a URL
                    requestUriAsObject = (r === undefined ? JSON.parse("{}") : JSON.parse('{"' + decodeURI(r).replace(/"/g, '\\"').replace(/&/g, '","').replace(/\=/g,'":"') + '"}'));
                    if (new RegExp(urlRegEx).test(decodeURIComponent(requestUriAsObject.sfmServiceName)))
                      realRequestUri = decodeURIComponent(requestUriAsObject.sfmServiceName);
                  }
              catch (e){console.log("DDS Interceptor exception:" + e.toString());}
                if (realRequestUri !== undefined) {
                      Object.defineProperty(this, "responseText", {writable: true});
                      Object.defineProperty(this, "response", {writable: true});
                      Object.defineProperty(this, "statusText", {writable: true});
                      Object.defineProperty(this, "status", {writable: true});
                      var real = new XMLHttpRequest();
                      real.fake = this;
                      real.onload = function(e) {
                          fake = real.fake;
                          fake.responseText = fake.response = real.response;
                          try { window.lastDDS = JSON.parse(real.response); }
                          catch (e) {console.log('DDS Interceptor received an invalid JSON response: ' + real.response);}
                          fake.statusText = "OK";
                          fake.status = 200;
                          fake.dispatchEvent(new Event('load'));
                      }
                      var url = realRequestUri.replace('<search>', requestUriAsObject.search);
                      real.open("GET", url);
                      real.send();
                    }
                  else
                    return send.apply(this, arguments); // Otherwise, pass thru all other XHR calls
            }
        })(XMLHttpRequest.prototype)
        console.log("DDS Interceptor code installed.");
      }
        CommentAdd your comment...
      1.  
        1
        0
        -1

        Hi Andrew,

        After consulting our product team I can confirm that this is not currently a feature and not possible to achieve without using the DDS option I suggested earlier.

        I have raised a ticket in engineering for this feature, but I cannot guarantee it will be something they provide for in the next release of Transact.

        In regards to Tim Stewart's answer, his solution refers to only handling data dynamically once (on init), rather than constantly refreshing/regenerating new data sets after the form has been opened.

        Regards,

        Michael

        1. Andrew Halliday

          Hi Michael,

          Thanks for putting this to the product team and getting an answer that's definitive.  Nevertheless, I'll watch out for new features along these lines but re-architect my current solution if I must.

          Here's hoping that such a change won't be too onerous considering a lot of the underlying presentation / data mechanics must already exist to do the DDS work.

          Andrew

        CommentAdd your comment...
      2.  
        1
        0
        -1

        Hi Andrew,

        From what I gather this doesn't seem possible. That is the autocomplete field specifies the data field as the source of the data and gathers the data as it initializes. Hence why you are only seeing the first instance. I suspect this is because a pre-pop on an autocomplete field is done once on form load.

        Can you explain the reason you wish to use a data field instead of a dynamic data service?

        1. Andrew Halliday

          Hi Michael,

          Yes, I'd figured that the reason for these symptoms were that the data was only being taken from the data field only once during form initialization.  I'd like to see if there is a way to re-initialize the autocomplete, or take any action that will allow me to refresh that data...even if it needs me to reach inside an Avoka JS data object and replace its copy of prepop data!

          The reason I'm doing this is security contexts. I need to call a web service from the actual client computer using the form.  The caller's identity and network context will determine the web service's response.  So as I understand it, a call to this web service cannot be made by TM on behalf of the user and then passed back to the form (DSS), unless I get into brokering credentials, which I'm trying to avoid.

          Andrew

        CommentAdd your comment...