javascriptjsond3.js

Using d3.js to submit multiple JSON requests and bind data to different elements


I am building a configuration page that allows a user to reconcile unmatched data by selecting a match from a list of possibilities. The list of items needing to be matched is generated by the backend, then a JS function requests the list of possible matches and populates the corresponding element. I'm currently using D3.js to do the selecting and updating.

As it stands, it looks like everything is attaching correctly from the .data() function, but only the last select is receiving its options.

Here is the JS code that retrieves the JSON and populates the select elements. It runs after the window loads

function attach_options() {
    opts = d3.selectAll("select[data-opt-table]")
    opts.each(function(p,j) {
        obj = d3.select(this)
        opt_table = obj.attr('data-opt-table')
        opt_field = obj.attr('data-opt-field')
        opt_disp = obj.attr('data-opt-disp')
        opt_lookup = obj.attr('data-opt-lookup') 
        d3.json("{% url 'get-opts' %}" + opt_table + '/' + opt_field + '/' + opt_disp + '/' + opt_lookup + '/').then(
            function(data) {
                options = obj.selectAll('option').data(data.opt_list)
                options.join('option')
                    .attr('value', d => d.pk)
                    .text(d => d.disp_value)
            }
        )
    })
}

Here is a sample JSON response:

{
  "opt_list": [
    {
      "pk": "DE-001",
      "disp_value": "Kent"
    },
    {
      "pk": "DE-003",
      "disp_value": "New Castle"
    },
    {
      "pk": "DE-005",
      "disp_value": "Sussex"
    }
  ]
}

Solution

  • The function you pass to d3.json is executed async. By the time it is called, the variable obj has been over-written by a subsequent execution of the loop. The easiest fix is to wrap d3.json in an additional function where you pass in obj. Here's what that would look like with an anon function:

    function attach_options() {
        opts = d3.selectAll("select[data-opt-table]");
        opts.each(function(p,j) {
            obj = d3.select(this);
            opt_table = obj.attr('data-opt-table');
            opt_field = obj.attr('data-opt-field');
            opt_disp = obj.attr('data-opt-disp');
            opt_lookup = obj.attr('data-opt-lookup'); 
            ((obj) => {
                d3.json("{% url 'get-opts' %}" + opt_table + '/' + opt_field + '/' + opt_disp + '/' + opt_lookup + '/').then(
                    function(data) {
                        options = obj.selectAll('option').data(data.opt_list)
                        options.join('option')
                            .attr('value', d => d.pk)
                            .text(d => d.disp_value);
                    }
                );
            })(obj);
        });    
    }
    

    Working example.

    Also, remember to use semi-colons with JavaScript.