Search code examples
javascriptknockout.jsknockout-mapping-plugin

Unable to get optionsValue to work with dependent dropdowns and the Knockout mapping plugin


I am a database developer (there's the problem) tasked with emitting JSON to be used with Knockout.js to render sets of dependent list items. I have just started working with Knockout, so this is likely something obvious that I am missing.

Here is the markup:

<select data-bind="options:data,
                   optionsText:'leadTime',
                   value:leadTimes">
</select>

<!--ko with: leadTimes -->
    <select data-bind="options:colors,
                       optionsText:'name',
                       optionsValue:'key',
                       value:$root.colorsByLeadTime">
    </select>
<!--/ko-->

Here is the test data and code:

var data = [
    {
        key:"1",
        leadTime:"Standard",
        colors:[
            { key:"11", name:"Red" },
            { key:"12", name:"Orange" },
            { key:"13", name:"Yellow" }
        ]
    },
    {
        key:"2",
        leadTime:"Quick",
        colors:[
            { key:"21", name:"Black" },
            { key:"22", name:"White" }
        ]
    }
]

var dataViewModel = ko.mapping.fromJS(data);

var mainViewModel = {
    data:dataViewModel,
    leadTimes:ko.observable(),
    colorsByLeadTime:ko.observable()
}

ko.applyBindings(mainViewModel); 

As this stands, it correctly populates the value attribute of the second select list. However, if I add optionsValue:'key' to the first select list then the value attribute for that is set correctly but the second select list renders as an empty list.

All I need is for the value attribute of the option tag to be set to the key value provided in the data, regardless of where the select list is in the set of dependent lists. I've looked at many articles and the docs, but this particular scenario (which I would think is very common) is eluding me.

Here is a jsfiddle with the data, JS, and markup as given above: http://jsfiddle.net/tnagle/Lyxjt11y/


Solution

  • To really see the issue, you can add the following code after the initialization of mainViewModel.

    mainViewModel.leadTimes.subscribe(function(newValue) { 
      console.log(newValue);
      debugger; 
    });
    

    Before adding the optionsValue:'key', line above will log the following output.

    Object {key: function, leadTime: function, colors: function}
    

    But after adding optionsValue:'key', it log the following output.

    "1"
    

    or

    "2"
    

    The reason it failed was because when you assign optionsValue: 'key' to the first select list, leadTimes property of your mainViewModel which before will contain an object that has property color, now will be set to a string object. Then the select list just failed to find color property from leadTimes that has changed to a string object.

    One of the way to make it work is by changing to this:

    var data = [
      {
        key:"1",
        leadTime:"Standard",
        colors:[
          { key:"11", name:"Red" },
          { key:"12", name:"Orange" },
          { key:"13", name:"Yellow" }
        ]
      },
      {
        key:"2",
        leadTime:"Quick",
        colors:[
          { key:"21", name:"Black" },
          { key:"22", name:"White" }
        ]
      }
    ]
    
    var dataViewModel = ko.mapping.fromJS(data);
    
    var mainViewModel = new function (){
      var self = this;
      self.data = dataViewModel;
      self.leadTimes = ko.observable();
      self.selectedKey = ko.observable();
      self.selectedKey.subscribe(function(selectedKey){
    	self.selectedData(ko.utils.arrayFirst(self.data(), function(item) {
            return item.key() == selectedKey;
        }));
      }, self); 
      self.colorsByLeadTime = ko.observable();
      self.selectedData = ko.observable();
    }
    
    ko.applyBindings(mainViewModel);
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.3.5/knockout.mapping.js"></script>    
    
    <select data-bind="options:data,
                   optionsText:'leadTime',
                   optionsValue:'key',
                   value:selectedKey">
    </select>   
      
    <!--ko with: selectedData -->
    <select data-bind="options:colors,
                       optionsText:'name',
                       optionsValue:'key',
                       value:$root.colorsByLeadTime">
    </select>
    <!--/ko-->