Pre-populating nested (cascading) dropdowns

https://groups.google.com/d/topic/knockoutjs/4bbUhl31gOY/discussion

by Dale Howard

HTML

<script src="http://rniemeyer.github.com/KnockMeOut/Scripts/jquery.tmpl.js"></script>
<script src="http://knockoutjs.com/downloads/knockout-2.2.1.debug.js"></script>
<script src="http://benalman.com/code/projects/jquery-bbq/jquery.ba-bbq.js"></script>
<table>
    <tr>
        <th>Name</th>
        <th>ParentOption</th>
        <th>ChildOption</th>
        <th></th>
    </tr>
    <tbody data-bind="template: { name: 'itemsTmpl', foreach: items }"></tbody>
</table>
<button data-bind="click: addItem">Add Item</button>
<hr />
<div data-bind="text: ko.toJSON(viewModel.items)"></div>
<script id="itemsTmpl" type="text/html">
    <tr > <td > <input data-bind = "value: name" /> </td>
        <td>
            <select data-bind="options: viewModel.parentOptions, optionsText: 'name', optionsCaption: 'choose...', optionsValue: 'name', value: parentOption" /> </td>
        <td>
            <select data-bind="visible: parentOption, options: childOptions, optionsText: 'name', optionsCaption: 'choose...', optionsValue: 'name', value: childOption" /> </td>
        <td>
            <button data-bind="click: function() { viewModel.removeItem($data); }">Delete</button > </td>
    </tr >
</script>

CSS

th, td {
    padding: 2px;
}

JavaScript

function Item(name, parentOption, childOption) {
    this.name = ko.observable(name);
    this.parentOption = ko.observable(parentOption);
    this.childOption = ko.observable(childOption);
    this.childOptions = ko.dependentObservable(function () {
        var parent = ko.utils.arrayFirst(viewModel.parentOptions, function (option) {
            return option.name === this.parentOption();
        }, this);

        return parent ? parent.childOptions : [];
    }, this);
};

//if necessary, remove the childOptions dependentObservable for our JSON output
Item.prototype.toJSON = function () {
    var copy = ko.toJS(this);
    delete copy.childOptions;
    return copy;
}

var viewModel = {
    parentOptions: [{
        name: "parentA",
        childOptions: [{
            name: "childA1"
        }, {
            name: "childA2"
        }]
    }, {
        name: "parentB",
        childOptions: [{
            name: "childB1"
        }, {
            name: "childB2"
        }]
    }, {
        name: "parentC",
        childOptions: [{
            name: "childC1"
        }, {
            name: "childC2"
        }]
    }],
    items: ko.observableArray(),
    addItem: function () {
        this.items.push(new Item("new"));
    },
    removeItem: function (item) {
        this.items.remove(item);
    }
};

viewModel.items.push(new Item("test", "parentA", "childA2"));
viewModel.items.push(new Item("test2", "parentC", "childC2"));


ko.applyBindings(viewModel);