Knockout - Cascading Dropdown

by Doug Hill

HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.2.0/css/font-awesome.min.css">
<div class="container-fluid">
    <section class="heirarchy">
        <div class="row">
            <div class="col-xs-12">
                 <h3>Hierarchy Filters:</h3>

            </div>
        </div>
        <div class="row">
            <section class="hrFilter">
                <div class="col-sm-1">
                    <div class="filterLabel form-group"><i class="fa fa-fw fa-sitemap"></i><span data-bind="text: hrCatLabel" data-helptip="filters-hierarchy" title="&lt;i class=&quot;fa fa-fw fa-lg fa-th-sitemap&quot;&gt;&lt;/i&gt; Hierarchy Category"></span>

                    </div>
                </div>
                <div class="col-sm-4">
                    <div class="filterValue form-group">
                        <div data-bind="css: {'noentry': hrCatSelected() === undefined, 'entry': hrCatSelected() !== undefined }, toggle: hrCatEdit, filterAnimate: hrCatEdit()" class="filterText"><span data-bind="text: hrCatSelected() === undefined ? 'nothing selected' : hrCatSelected"></span>

                        </div>
                        <div class="filterInput">
                            <select name="hrCatSelect2" data-bind="options: hrCatValues, optionsValue: 'id', optionsText: 'text', optionsCaption: '', value: hrCatSelected, uniqueName: true, select2: {allowClear: false, placeholder: 'select hierarchy category'}" class="form-control input-sm"></select>
                        </div>
                    </div>
                </div>
                <div class="col-sm-1 col-sm-offset-1">
                    <div class="filterLabel form-group"><i class="fa fa-fw fa-sitemap"></i><span data-bind="text: hrItemLabel" data-helptip="filters-hierarchy" title="&lt;i class=&quot;fa fa-fw fa-lg fa-th-sitemap&quot;&gt;&lt;/i&gt; Hierarchy Item"></span>

...

JavaScript

/*global ko, jQuery*/
ko.bindingHandlers.select2 = {

    init: function (element, valueAccessor, allBindings) {
        "use strict";

        var obj = valueAccessor(),
            lookupKey = allBindings.lookupKey,
            $element = jQuery(element),
            select2Options;

        select2Options = allBindings.get('select2') || {};

        $element.select2(select2Options);

        if (lookupKey) {

            var value = ko.utils.unwrapObservable(allBindings.value);
            $element.select2('data', ko.utils.arrayFirst(obj.data.results, function (item) {
                return item[lookupKey] === value;
            }));
        }

        if (allBindings.value) { // FIX no initial values   
            allBindings.value.subscribe(function (v) {
                $element.trigger('change');
            });
        }
        ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
            $element.select2('destroy');
        });
    },

    update: function (element, valueAccessor, allBindings) {
        "use strict";

        var $element = jQuery(element),
            value = ko.utils.unwrapObservable(allBindings.value || allBindings.selectedOptions);

        if (value) {
            $element.select2('val', value);
        }
    }
};

var hrOptionsJSON;

var BoxVM = {
    hrCatLabel: ko.observable('Category'),
    hrCatValues: ko.observable(),
    hrCatSelected: ko.observable(),
    hrCatEdit: ko.observable(false),
    hrItemLabel: ko.observable('Options'),
    hrItemValues: ko.observable(),
    hrItemSelected: ko.observable(),
    hrItemEdit: ko.observable(false)
};

jQuery.get({
    url: '/echo/json/',
    dataType: 'JSON',
    success: function (response) {
        hrOptionsJSON = response;
        BoxVM.hrCatValues = hrOptionsJSON;
    },
    data: [{
        "id": "Organization",
        "text": "Organization",
        "children": [{
            "id": "Mobility Customer Service",
            "text": "Mobility Customer Service"
...