makoshichi
makoshichi

Reputation: 2390

Populating one combobox from another using ASP.NET and KnockoutJS

I know it has been asked before, but it I've been at it for hours and I couldn't find a viable solution. Summarising, I receive an JSON string from the Server (C#), like this:

function CallFromServer() {
$.ajax({
    type: "POST",
    async: false,
    url: "Default.aspx/GetTest",
    data: "{ }",
    contentType: "application/json; charset=utf-8",
    dataType: "json",
    success: function (msg) {
            locationsList = jQuery.parseJSON(msg.d);
        }
    });
};

Pretty vanilla, right? Each entry of locationsList consists of a string, which is a state and another list, consisting of the cities in this given state. I'm pretty new to KnockoutJS (and it's not even my project, I'm just doing a small freelance), so after messing around for a while, I came to this conclusion on how to populate comboboxes:

HTML:

<select id="stateSelect" onchange="GetCities()" style="background-color:#69bed2; width:50px;" data-placeholder="..."
        data-bind="options: stateValue" name="state" class="chosen-select" tabindex="1">
</select>

<select id="citySelect" style="background-color:#69bed2; width:143px;" data-placeholder="Select a state"
              data-bind="options: cityValue" name="city" class="chosen-select" tabindex="2">
</select>

JS:

function CalculatorViewModel()
{
    var self = this;
    CallFromServer(); //Mine
    GetStates(); //Mine

    self.percentBonus = ko.observable(3);
    self.typedValue = ko.observable("0");
    self.bonusValue = ko.observable('');
    self.messageValue = ko.observable("");
    self.stateValue = ko.observableArray(stateArray); //Mine
    self.cityValue = ko.observableArray(cityArray); //Mine

    (...) //A lot of stuff
}

What isn't commented as "Mine" was pretty much there already and along with some reading of the KOJS documentation, I used it in order to try to achieve what I wanted.

Ok, now let's get to the gist of it. One might notice that I used

onchange="GetCities()

in the first select. Here's it's code:

function GetCities() {
    cityArray.clear();
    cityArray[0] = '';
    var currentState = $('#stateSelect :selected').text();
    $.each(locationsList, function (i, e) {
        if (e.stateName == currentState) {
            $.each(e.cityList, function (j, city) {
                cityArray[j] = e.cityList[j].cityName;
            });
        }
    });
};

As espected, whenever this onChange event is raised when a state is changed, self.cityArray updates itself to the proper value. However, the combobox to which it is bound won't refresh, no matter what. I've read a lot about different ways to do it, but I feel that I'm so close with this approach that starting from scratch is not an option.

Does anyone have any ideas of what I'm missing here?

BTW, don't worry about my stateArray binding, it works just fine. RENDERING the new set of options provided by the cityArray after selecting a state is what is proving to be troublesome.

Upvotes: 0

Views: 541

Answers (1)

Jeroen
Jeroen

Reputation: 63760

I think you may be confused as to how Knockout works, it may help going through the turorials. You seem to be doing two things that go against the idea of KO that are part of your problem:

  • don't directly wire the onchange event like that, but use some Knockout construct (for example a subscription) for that;
  • you seem to be clearing and repopulating the cityArray instead of the cityValue observable array; the UI will only respond to changes in the latter

Furthermore, you'll need:

  • a selectedOptions binding to bind an observable to the selected state(s)

Some changes I'd at least suggest, in respective order:

<select id="stateSelect" 
        style="background-color:#69bed2; width:50px;" 
        data-placeholder="..."
        data-bind="options: stateValue, selectedOptions: selectedStates" 
        name="state" 
        class="chosen-select" 
        tabindex="1">
</select>

The view model needs to be extended with something to hold the selection of states:

function CalculatorViewModel()
{
    // Added:
    self.selectedStates = ko.observableArray([]);;       
}

Also remove the onchange direct event wiring. Replace it by something along these lines, subscribing to the selectedOptions:

// Use a KO subscription:
self.selectedStates .subscribe(function(newStateValue) {

    // Clear the *observable* array
    self.cityValues.removeAll();

    $.each(locationsList, function (i, e) {
        if (e.stateName == newStateValue) {
            $.each(e.cityList, function (j, city) {
                // Push into the observable array so the UI gets updated
                self.cityValues.push(e.cityList[j].cityName);
            });
        }
    });
});

Here's a fiddle to demo how this works.

Upvotes: 2

Related Questions