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">
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/3.5.2/select2.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/select2/3.5.2/select2-bootstrap.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/select2/3.5.2/select2.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.2/jquery-ui.js"></script>
<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="<i class="fa fa-fw fa-lg fa-th-sitemap"></i> 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>
...
CSS
.select2-bs-validation(@text-color: @input-color;
@border-color: @input-border;
@background-color: @input-bg;
@input-color: @white;
@hover-color: @enchanting) {
.select2-choice, .select2-search-field {
color: @text-color;
}
.select2-container-multi .select2-search-choice .select2-search-choice-close:hover, .select2-container .select2-choice .select2-search-choice-close:hover {
color: @text-color;
}
// Set the border and box shadow on specific inputs to match .select2-choice, .select2-container, .select2-container-multi, .select2-search-field, .select2-container-multi .select2-choices .select2-search-field {
border-color: @border-color;
}
.select2-container.select2-dropdown-open {
background: @input-color;
}
.input-group-addon {
color: @text-color;
border-color: @border-color;
background-color: @background-color;
}
.form-control-feedback {
color: @text-color;
i.fa-lg {
vertical-align: -17%;
}
}
}
// PRIMARY WRAPPER FOR ALL SELECT2 COMPONENTS .select2-container {
margin: 0 !important;
padding: 0 !important;
border: 0;
&.select2-container-active {
border: 1px solid @enchanting;
outline: 0;
box-shadow: 0 0 6px rgba(68, 200, 245, 0.3);
.border-radius-kit(@border-radius-base, @border-radius-base, @border-radius-base, @border-radius-base);
.select2-choice, .select2-choices {
border: none;
background: transparent;
outline: 0;
&:focus {
border: none !important;
outline: 0;
}
}
&.select2-container {
height: @input-height-base;
}
&.select2-container-multi {
// box-shadow: none;
}
.select2-choices {
.select2-search-field {
border: none;
}
}
}
// DROPDOWN OPEN...
JavaScript
/*global ko, jQuery*/
ko.bindingHandlers.toggle = {
init: function (element, valueAccessor) {
"use strict";
var value = valueAccessor();
if (!ko.isObservable(value)) {
throw new Error('Toggle binding should be used only with observable values.');
}
ko.applyBindingsToNode(element, {
click: function () {
value(!value());
}
});
},
update: function (element, valueAccessor) {
"use strict";
ko.utils.toggleDomNodeCssClass(element, 'active', ko.unwrap(valueAccessor()));
}
};
ko.bindingHandlers.filterAnimate = {
update: function (element, valueAccessor) {
"use strict";
// WHEN VALUE CHANGES, SLOW FADE ELEMENT IN OR OUT
var value = ko.utils.unwrapObservable(valueAccessor()),
//SET THE ELEMENT VAR
$element = jQuery(element),
$el1 = $element,
$el2 = $el1.next(),
$el3 = $el2.next(),
// WHETHER OR NOT VALUE IS OBSERVABLE
valueUnwrapped = ko.unwrap(value);
// MANIPULATE THE DOM
if (valueUnwrapped === true) {
$el1.slideUp(350);
$el2.delay(450).show('drop', function () {
jQuery(this).find('input').first().focus();
});
} else {
$el2.hide('drop');
$el1.delay(450).fadeIn(350);
// $el1.effect('highlight', {color: 'rgba(76, 255, 0, 0.11)'}, 2000);
}
}
};
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 =...