Multi-level lists in ODK Survey

We have a survey we are creating in 2.0 tools with numerous multi-level lists. We’re calling on csv files, very much like the regions.csv file example in XLSX Converter documentation. However, our lists go beyond two levels. For example, we have state, county and, city data in a csv and need to select states, counties within states and then cities within counties. We also want each state, county, and city to show up only once in the select values in Survey. We’ve got the choice filters functioning, the first level to work, and the last is no problem, but intervening levels (e.g. county) have duplicates showing up.

Here’s what we’re using in the callback:

state:

“_.chain(context).pluck(‘state’).uniq().map(function(state){
return {name:state, label: state, data_value: state, display: {text:state}};
}).value()”

county:

"_.map(context, function(place){
place.name = place.county;
place.label = place.county;
place.data_value = place.name;
place.display = {text:place.label};
return place;
})
"

city:

"_.map(context, function(place){
place.name = place.city;
place.label = place.city;
place.data_value = place.name;
place.display = {text:place.label};
return place;
})
"

Various attempts to stick .uniq() in the state callback have been to no avail, since the array is unique even when the state is not.

Any help much appreciated.

I don’t know much about ODK Survey, but I think I may be able to help out a little bit with your code here anyway. Keep that in mind, though, in case I sound like I don’t know what I’m talking about: I don’t! :slight_smile:

So, my first reaction is that your state-level map is generating and returning new objects, whereas your _.map for county and city are modifying the original source data elements themselves and then returning them. This probably works, but probably isn’t as precise as it could be, since if multiple pieces of code try to do this you’re going to get a lot of weird results. I’d recommend modifying them to look more like the state code; eg:

_.map(context, function(place) {
  return { name: place.county, label: place.county, data_value: place.name, display: { text: place.label } };
});

Incidentally, you probably actually want data_value to be place.county rather than place.name for the county select, so that you actually get the name of the county rather than the name of the city.

As for your broader problem, you’re right that .uniq doesn’t work well for arrays of data objects. It also doesn’t take any smart parameters as to how to read into the data to find a value to consider unique. So you’ll have to do a bit of the work yourself:

var seen = {};
_.chain(context).filter(function(place) {
    var keep = (seen[place.county] !== true);
    seen[place.county] = true;
    return keep;
).map(function(place) {
  return { name: place.county, label: place.county, data_value: place.name, display: { text: place.label } };
}).value();

We create a little dictionary to keep track of what counties we’ve seen. Then for each county, we check if we’ve seen it, mark it as seen, and then tell the filter function whether to keep that item depending on whether we’d seen it. Then we do the map you’re already doing.

Hopefully this was helpful!

Thanks for your suggestion @issa! So this definitely has the right idea. I got:

var seen = { };
_.chain(users).filter(function(place) {
var keep = (seen[place.state] !== true);
seen[place.state] = true;
return keep; }).map(function(place) {
return { name: place.state, label: place.state, data_value: place.state, display: { text: place.state } };
}).value();

to work as desired in a lodash tester. However, when I try to use this in ODK Survey as a callback it does not like the var seen = { }; start, and putting that inside the chain (at least where I tried it) then defeated the whole de-duplicating. Any ideas on making this all one chain in hopes that makes ODK happy? Much appreciated!

Hrm. Okay. That’s unfortunate. How about if you wrap the whole thing so it’s one statement? As follows:

(function() {
  var seen = { }; 
  return _.chain(users).filter(function(place) {
    var keep = (seen[place.state] !== true);
    seen[place.state] = true;
    return keep; })
  .map(function(place) {
    return { name: place.state, label: place.state, data_value: place.state, display: { text: place.state } };
  }).value();
})();

Thanks very much @issa! Wrapping things together was what was needed, and I just had no idea how to do that successfully! I did have to modify to keep the right data and make the choice filter kick in for the state level when doing the county level. Here’s what worked in the queries, callback for the county level:

(function() {
var seen = { };
return _.chain(context).filter(function(place) {
var keep = (seen[place.state] !== true);
seen[place.state] = true;
return keep; })
.map(function(place) {
place.name = place.state;
place.label = place.state;
place.data_value = place.name;
place.display = {text:place.label};
return place;
}).value();
})()

This successfully gave me a unique list of counties within the states I had selected on the previous level, even when there were repeated observations for counties in the csv of the multi-level list because they contained multiple cities.

Hooray! I’m glad it worked out.