Cascading select lists with KnockoutJS

by Doug Hill

HTML

<script src="https://dl.dropbox.com/u/6378864/countries.js"></script>
<script src="http://knockoutjs.com/downloads/knockout-2.2.1.js"></script>
<p>Region:
    <select data-bind="options: selectedRegion.regions, optionsText: 'Name', optionsValue: 'ID', value: selectedRegion"></select>
</p>
<p>Country:
    <select data-bind="options: countries, optionsText: 'Name', optionsValue: 'ID', value: selectedCountry"></select>
</p>
<p>City:
    <select data-bind="options: cities, optionsText: 'Name', optionsValue: 'ID', value: selectedCity"></select>
</p>
<hr/>
<pre data-bind="text: ko.toJSON($root, null, 2)"></pre>

JavaScript

(function () {

    var ViewModel = function () {

        this.selectedRegion = ko.observable(1); // select Australia and New Zealand by default.
        this.selectedRegion.regions = newData;

        this.selectedCountry = ko.observable();
        this.selectedCity = ko.observable();

        // resets
        this.selectedRegion.subscribe(function () {
            this.selectedCountry(undefined);
        }, this);

        this.selectedCountry.subscribe(function () {
            this.selectedCity(undefined);
        }, this);

        var getById = function (items, id) {
            return ko.utils.arrayFirst(items, function (item) {
                return item.ID === id;
            });
        };

        this.countries = ko.computed(function () {
            var region = getById(this.selectedRegion.regions, this.selectedRegion());
            return region ? ko.utils.arrayMap(region.Countries, function (item) {
                return {
                    ID: item.ID,
                    Name: item.Name
                };
            }) : [];
        }, this);

        this.cities = ko.computed(function () {
            var region = getById(this.selectedRegion.regions, this.selectedRegion());
            if (region) {
                var country = getById(region.Countries, this.selectedCountry());
                if (country) {
                    return country.Cities;
                }
            }

        }, this);
    };

    var model = new ViewModel();
    ko.applyBindings(model);

})();