Search code examples
jsviews

JsView Select does not display selected option immediately after clicking on it


I ran into a case where selecting of an option that is data bound to my javascript data object resulted into a different option being shown as selected.

Example data object:

var data = {
    "person": { "no" : 2 },
    "options": [
        {"no":0,"t":"null"},
        {"no":1,"t":"a"},
        {"no":2,"t":"b"},
        {"no":3,"t":"c"}
    ]
};

Corresponding Select with the data binding setup:

<script id="SelectTemplate" type="text/x-jsrender">
    <select id="MySelect" data-link="person.no">
        <option value=""></option>
        {^{for options}}
        <option data-link="{:t} value{:no} selected{:no === ~root.person.no}"></option>
        {{/for}}
    </select>
</script>

I have created a JsFiddle here to play with my setup. Can anyone please explain to me why this is happening? Or what I am doing wrong?

Note: I would like to share my current workaround just in case someone else happens to run into this problem. I am listening to property change event on my select data bound object (i.e. person). Here is what it looks like:

$(data.person).on('propertyChange', function (event, eventArgs) {
    $("#MySelect").val(eventArgs.value);
});

Solution

  • The problem is that you are using integer values: no:1 rather than no:"1".

    Those values get converted to string when set as values of the corresponding option element. So when the user selects a new item, the value person.no is set to a string "2", say, which fails the comparison :no === ~root.person.no.

    So here are four alternative fixes:

    1: If you don't want to support having dynamic changes to the t or no values, then you don't need to data-link those values on the option, but can set them statically in the initial rendering:

    <select data-link="person.no">
        {^{for options}}
            <option value="{{:no}}">{{:t}}</option>
        {{/for}}
    </select>
    

    -but person.no will still get switched to a string value after selection, unless you use a converter: <select data-link="{:person.no:toNum}"> (see #4 below...)

    2: If you want to keep the data-bound values, you can change from using from integers to using string values.

    3: Keep the integers and simply make the comparison non-strict: <option data-link="{:t} value{:no} selected{:no == ~root.person.no}"></option> - but again, person.no will get switched to a string value after selection...

    4: Keep the integers and convert back to integer, so the number type is preserved:

    $.views.converters("toNum", function(val) {
        return +val;
    })
    

    and

    <select data-link="{:person.no:toNum}">
        {^{for options}}
            <option data-link="{:t} value{:no} selected{:no === ~root.person.no}"></option>
        {{/for}}
    </select>
    

    (So now person.no will have a number value after selection...)

    BTW - side note: for your workaround you could have used an alternative - specific for changes to person.no, so it will not break if other properties of person get changed:

    $.observe(data.person, "no", function (event, eventArgs) {
        $("#MySelect").val(eventArgs.value);
    });
    

    rather than

    $(data.person).on('propertyChange', function (event, eventArgs) {
        $("#MySelect").val(eventArgs.value);
    });