Pre-populating nested (cascading) dropdowns

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

by bvotcode

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);