10
$\begingroup$

I'm trying to create a dynamic InputField that would autocomplete the address as I type it into the field, by calling Google Maps' GeoCoding API on every change of the field's value.

How can I make AJAX-style HTTP calls when the value of the InputField changes?

So for the only approach that I'm aware of is by using UpValues: Detecting changes of variables' values

Here's my code using UpValues:

DynamicModule[{addr, geo},

 addr /: Set[addr, x_] := (
   (
    OwnValues[addr] = {HoldPattern[addr] :> x}; 
    Print["Value changed"];
    geo = (Import["https://maps.googleapis.com/maps/api/geocode/json",
          "JSON", "RequestParameters" -> {"address" -> x}] /. 
    List -> Association)["results"]
   ); 
   addr
  );

 addr = "555 Mission St, San Francisco";

 Column@
   {
   InputField[Dynamic[addr], String, ContinuousAction -> True],
   Dynamic[addr], Dynamic[geo["formatted_address"]]
   }
 ]

It kind of works, but for some reason not every change of the field's value triggers an API call.

Hence two questions:

  1. Why are some of the change events lost?

  2. Is there a better way to do AJAX-style HTTP calls when the value of a dynamic variable changes?

Also, these API calls via Import take ~200 ms, while they take much less time if I make them in a browser. Is there any way to speed them up?

$\endgroup$
14
$\begingroup$

Just like Albert I recommend using the second argument of Dynamic. Furthermore I recommend that you embrace the first A in AJAX, which stands for "asynchronous", so the kernel isn't busy while it collects the data (this might be why some change events are lost with your code).

You can see how well the second argument of Dynamic works with this example:

Column[{
  InputField[
   Dynamic[s, (choices = RandomInteger[100, 10]; s = #) &],
   String,
   ContinuousAction -> True
   ],
  Dynamic@choices
  }]

Now I'll take the same code and add the code for fetching data asynchronously:

url = "https://maps.googleapis.com/maps/api/geocode/json";
callback[_, "data", data_] := choices = extractsAddresses@First@FromCharacterCode[data]

Needs["GeneralUtilities`"]
extractsAddresses[data_] := ToAssociations[ImportString[data, "JSON"]][["results", All, "formatted_address"]]

fetchChoices[addr_] := URLFetchAsynchronous[
  url,
  callback,
  "Parameters" -> {"address" -> addr}
  ]

Column[{
  InputField[
   Dynamic[s, (fetchChoices[#]; s = #) &],
   String,
   ContinuousAction -> True
   ],
  Dynamic@choices
  }]

enter image description here

If you want to you can pair this strategy with the autocomplete InputField interfaces available here.

You can also take note of how I converted JSON into Association, which is undocumented but has been mentioned e.g. here.

$\endgroup$
  • 1
    $\begingroup$ I just learned three new things about Mathematica from your and Albert's answers. Thanks so much!! $\endgroup$ – verse Jun 18 '15 at 20:38
  • $\begingroup$ I have a quick clarifying question. When I wrap your code in a DynamicModule and declare choices as a local variable, it stops working. Do you know why that might be the case? $\endgroup$ – verse Jun 19 '15 at 17:18
  • $\begingroup$ Well, depending on how you did it there are a few things that have to be fixed. I'm not sure about this atm, so your best bet is to ask a new question about it. $\endgroup$ – C. E. Jun 19 '15 at 18:03
  • $\begingroup$ Thanks, Pickett. I posted a follow-up question here: mathematica.stackexchange.com/questions/86393/… $\endgroup$ – verse Jun 19 '15 at 20:13
10
$\begingroup$

I think the standard way to do this is to use the second argument of Dynamic, e.g. like so:

With[{
  getCompletion = Function[
    Import[
     "https://maps.googleapis.com/maps/api/geocode/json", "JSON",
     "RequestParameters" -> {"address" -> #}
    ] //. {{r__Rule} :> (Association[r])}
  ]},
  DynamicModule[{
      addr = "555 Mission St, San Francisco",geo
    },
    geo = getCompletion[addr];
    Column@{
      InputField[
        Dynamic[addr, (addr = #; geo = getCompletion[#]) &],
        String, ContinuousAction -> True
      ],
     Dynamic[addr], Dynamic[geo[["results", 1, "formatted_address"]]]
   }
 ]
]
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.