DemoJam XForms with interactive Map

At the end of a very enjoyable day at XMLAmsterdam a few weeks ago, there was a DemoJam where you could demo something for 5 minutes. In the spirit, and not because my teenage son wanted an iPad mini and that was coincidently the first prize, I brought along a little demo of a solr query linked to a map rendered in an XForm.

The very worthy winner was Marc van Grootel with his 1:30s video “XML Rock Star“. If you haven’t seen it, go look now – you’ll thank me, and then come back

My demo started out as simple XForm which talked to a SOLR instance to do faceted search on some project data. I used the excellent XSLTForms (rev 580). Initially I returned a list, but one thing that came out of demo’ing was that for things like the projects, which had a location, it was easier to look at a map.

Untitled

It would have been pretty easy to replace the list with a map as an output, but I wantedsome way to make the map part of the search. If you zoomed out you should get more results and if you scrolled results outside the visible map should be excluded. Also, it should work the other way; if you used a facet or a search term, the map should scale to show those results, so the map should be a control and a display.

On the XForm side, the SOLR search is controlled by a ‘query’ instance and results are poured into a ‘results’ instance. The initial query is below:

<xf:instance id="query" xmlns="">
<data>
<q>id:*:*</q>
<facet>true</facet>
<rows>1000</rows>
<sort>id desc</sort>
<start>0</start>
<facet.field>trust_t</facet.field>
<facet.field>area</facet.field>
<facet.field>status</facet.field>
<facet.mincount>1</facet.mincount>
<fq id='0'>location:[50.84757295365389,-5.09765625 TO 54.18815548107151,2.373046875]</fq>

There are three facets and a facet-query <fq/> set to the whole of the UK. This is the one linked to the map, and hence the only one with an id. (The form inserts other <fq/>’s in and out depending on what users selects).

Inside the <model/> there is a custom <event/> triggered by JS on the map side. Note for this to work, your <model/> must have an id. The <event/> updates the filter to the new bounds of the map (i.e. the current ‘corners’) and re-submits the search. All made possible with XForms 2.0 which allows properties in events and explained to me patiently by Alain Couthures.

<xf:action ev:event="callbackevent">
 <xf:setvalue ref="instance('query')/fq[@id='0']" value="concat('location:[',event('latitude'), ' TO ', event('longitude'), ']')"/>
 <xf:send submission="solr"/>
 </xf:action>

The submission calls SOLR with the current query and gets the result set. If there are no problems it calls some JS to re-plot the map from the current results:

<xf:submission id="solr" method="get" replace="instance"
 instance="results" ref="instance('query')" action="http://localhost:8080/lux/collection1/select">
 <xf:action ev:event="xforms-submit-done">
 <xf:load resource="javascript:rePlot()"/>
 </xf:action>
 </xf:submission>

Now to the JS side. It’s a simple thing to add Leaflet.js maps to an XForm; tag a div and call the JS libraries at the end of the <body/> as per their examples. We’ll need a bit of custom JS (only a little bit) called when we move the map or zoom:

function getViewPort() {{
    //Get current corners
    var bounds=map.getBounds();
    var minll=bounds.getSouthWest();
    var maxll=bounds.getNorthEast();
    //Call xsltforms model named 'test' and hence <event/>
    XsltForms_xmlevents.dispatch(document.getElementById("test"),
        "callbackevent", null, null, null, null,
        {
            latitude: minll.lat + ',' + minll.lng,
            longitude: maxll.lat + ',' + maxll.lng
         });
    }
 }

That’s pretty much it. There’s more code on the JS side to call the function when the map moves or zooms and to populate the map from the results; handled very very poorly in the demo by a hidden <ul/>.

This is very much a work in progress but it does expose an interesting technique for interacting between JS and XForms layers. Another point is that it’s also a fix for the issue of not being able to use <xf:load/> with a callback. For example, the geolocation API in HTML5 to get current position could be rendered like so:

<script type="text/javascript">
  function asynch() {
    navigator.geolocation.getCurrentPosition(show_map);
  }
  function show_map(position) {
    XsltForms_xmlevents.dispatch(document.getElementById("modelid"),
        "callbackevent", null, null, null, null,
            {
                latitude: position.coords.latitude,
                longitude: position.coords.longitude
            });
  }
 </script>

If you want to download the full code, it’s available on GitHub . However it won’t work out of the box as it needs the SOLR data. It should be amenable to any similar data though with minor mods. Have fun.

Advertisements