Forum Stats

  • 3,780,926 Users
  • 2,254,456 Discussions
  • 7,879,496 Comments

Discussions

Multi select does not work when binding options from REST call

2930948
2930948 Member Posts: 5
edited Apr 12, 2016 2:25PM in Oracle JET

Hello,

I am having trouble with multi select. The problem is when I get the data for the options from the REST call then upon selecting the option it shows it's value in the select and not the label.

Here is some example code for the problem (for the simplicity of the example let's assume that the view model is properly bind to the select element, $ variable is jQuery object, url is variable containing the endpoint url of the server, the REST call is successful and the returned data is in following format:

     [{value: '1', label:'Label for item 1'}, {value: '2', label: 'Label for item 2'}, {value: '3', label: 'Label for item 3'}]

):

HTML snippet:

<label for="select">Select with options array</label>
<select id="select" data-bind="ojComponent: {component: 'ojSelect', options: browsers, multiple: true,
                                             rootAttributes: {style:'max-width:20em'}}">
</select>

and Javascript

function selectModel () {
    var self = this;
    this.browsers = ko.observableArray([]);
     $.getJSON(url, function(data) {
          self.browsers(data);
     });
  }

The problem is that in this example the options in the select are correct - Label for item 1, Label for item 2, .... But when one of them is selected then inside the select box it's value is shown eg. '1' instead of the label. This is problem only with multi select and when setting options in the callback from the REST call, single select works fine.

Any ideas how to solve this?

Thank you

Vasek Stebra

Best Answer

  • Jim.Marion-Oracle
    Jim.Marion-Oracle Member Posts: 929
    edited Apr 6, 2016 11:51AM Accepted Answer

    I put together a jsfiddle with your example and then started fiddling with it. It is very interesting that just commenting out the window.setTimeout makes it behave properly. This makes me question the way the bindings and observables behave.

    I noticed that you are completely replacing the array referenced by the observable. In most cases, that seems to be acceptable. I have trouble with that sometimes, though, so I use the ko helper methods to update the originally bound array rather than replace the array. Here is a jsFiddle that uses ko.utils.arrayPushAll and valueHasMutated to push the Ajax response into the observable's existing array. This version works the way you expect:

    https://jsfiddle.net/jmarion/8079eeoc/

Answers

  • John 'JB' Brock-Oracle
    John 'JB' Brock-Oracle Posts: 2,710 Employee
    edited Apr 5, 2016 12:30PM

    Hi Vasek,

    Changing your data to look like this:

    [

    {"value": "1", "label":"Label for item 1"},

    {"value": "2", "label": "Label for item 2"},

    {"value": "3", "label": "Label for item 3"}

    ]

    Corrects the behavior in my test code.  What you have shown is not valid JSON.

    Hope that helps

  • 2930948
    2930948 Member Posts: 5
    edited Apr 6, 2016 6:58AM

    Hi John,

    thank you for your reply. Yes what I shown is not valid JSON, what I shown is the JSON already converted to Javascript array - I apologize for misunderstanding - didn't realize that. However in my code I use valid JSON (generated by json_encode function in PHP) and it still does not work as expected.

    Let's take the JSON out of the game and try this simpler example, which simulates the delay when doing the REST call and also does not work as expected. After the 2 second, the options for the select are set correctly, but again when one of them is selected it's value instead of the label appears in the select box.

    <label for="select">Select with options array</label>  
    <select id="select" data-bind="ojComponent: {component: 'ojSelect', options: browsers, multiple: true,  
                                                 rootAttributes: {style:'max-width:20em'}}">  
    </select>
    

    function selectModel () {  
         var self = this;  
         this.browsers = ko.observableArray([]);  
         window.setTimeout(function(){
              var data = [{value: '1', label:'Label for item 1'}, {value: '2', label: 'Label for item 2'}, {value: '3', label: 'Label for item 3'}];
              self.browsers(data);
         }, 2000);
     }  
    

    Thank you

    Vasek

  • Jim.Marion-Oracle
    Jim.Marion-Oracle Member Posts: 929
    edited Apr 6, 2016 11:51AM Accepted Answer

    I put together a jsfiddle with your example and then started fiddling with it. It is very interesting that just commenting out the window.setTimeout makes it behave properly. This makes me question the way the bindings and observables behave.

    I noticed that you are completely replacing the array referenced by the observable. In most cases, that seems to be acceptable. I have trouble with that sometimes, though, so I use the ko helper methods to update the originally bound array rather than replace the array. Here is a jsFiddle that uses ko.utils.arrayPushAll and valueHasMutated to push the Ajax response into the observable's existing array. This version works the way you expect:

    https://jsfiddle.net/jmarion/8079eeoc/

  • 2930948
    2930948 Member Posts: 5
    edited Apr 6, 2016 12:22PM

    Hello Jim,

    thank you, your solution works great for me.

  • John 'JB' Brock-Oracle
    John 'JB' Brock-Oracle Posts: 2,710 Employee
    edited Apr 12, 2016 2:25PM

    The other option for trying to control when the DOM is loaded versus when the data is ready, is to use a Knockout virtual "if" binding.

    Using your code, it would look like this in the HTML

    <!-- ko if: dataReady -->
    <label for="select">Select with options array</label>   
    <select id="select" data-bind="ojComponent: {component: 'ojSelect', options: browsers, multiple: true,   
                                                rootAttributes: {style:'max-width:20em'}}">   
    </select>
    <!-- /ko -->
    

    Then in the JavaScript you add the ko observable for dataReady and initialize it to false.  Once the data has been returned, you set the value to true.  I modified your getJSON call to use the Promise ( .then ) provided by jQuery as well in the code below.

    function selectModel () {  
        var self = this;
         self.browsers = ko.observableArray([]);  
         self.dataReady = ko.observable(false);
    
         $.getJSON(url).then(function(data) {  
              self.browsers(data);
              self.dataReady(true);  
         });  
      }  
    

    Hope this helps others down the road.  It's a really common use case.