Cascading Select

Cascading/filtering dropdowns plugin.

HTML

<select id="first">
    <option value="selector">Select a filter</option>
    <option value="1">Filter 1</option>
    <option value="2">Filter 2</option>
    <option value="3">Filter 3</option>
</select>
<select id="second">
    <option filter="selector" value="selector">Select a value</option>
    <option filter="1" value="1">(1) - Value 1</option>
    <option filter="1" value="2">(1) - Value 2</option>
    <option filter="1" value="3">(1) - Value 3</option>
    <option filter="2" value="4">(2) - Value 4</option>
    <option filter="2" value="5">(2) - Value 5</option>
    <option filter="2" value="6">(2) - Value 6</option>
    <option filter="2" value="7">(2) - Value 7</option>
    <option filter="3" value="8">(3) - Value 8</option>
    <option filter="3" value="9">(3) - Value 9</option>
    <option filter="3" value="10">(3) - Value 10</option>
</select>
<select id="third">
    <option value="selector">Select a value</option>
    <option filter="1" value="1">(1) - Value 1</option>
    <option filter="1" value="2">(1) - Value 2</option>
    <option filter="1" value="3">(1) - Value 3</option>
    <option filter="2" value="4">(2) - Value 4</option>
    <option filter="2" value="5">(2) - Value 5</option>
    <option filter="2" value="6">(2) - Value 6</option>
    <option filter="2" value="7">(2) - Value 7</option>
    <option filter="3" value="8">(3) - Value 8</option>
    <option filter="3" value="9">(3) - Value 9</option>
    <option filter="3" value="10">(3) - Value 10</option>
</select>

JavaScript

$.filterSelect = {
    config: {
        target: "",
        //if target = "" then only the beforeChange, afterChange and backChaining are executed
        targetAttribute: "filter",
        //attribute to filter on
        defaultValue: "selector",
        hideDefault: true,
        //if true, removes the default selector when it is deselected
        backChaining: false,
        //if true filters back up the heirachy, must call filterSelect({backChaining: true}) after the call targeting the last select
        backTarget: "",
        onBeforeChange: null,
        //function of the form (change event, target element)
        onAfterChange: null,
        //function of the form (change event, target element)
        originalOptions: null
    }
};
$.fn.filterSelect = function(config) {
    //last argument is for backChaining
    config = $.extend({}, $.filterSelect.config, config, $(this).data("filterSelect"));
    $(this).data("filterSelect", config);
    if (config.backChaining && config.target !== "") $(config.target).data("filterSelect", $.extend({}, $(config.target).data("filterSelect"), {
        backTarget: $(this)
    }));
    $(this).change(function(event) {
        var config = $(this).data("filterSelect");
        if (config.originalOptions === null && config.target != "") {
            config.originalOptions = $(config.target + " option")
        }; //set the original options
        if (config.onBeforeChange && config.onBeforeChange.call($(this), event, $(config.target)) === false) {
            return this;
        } //stops the change occuring, say if invalid change
        if ($(config.target + " option[" + config.targetAttribute + "!=" + $(this).val() + "]").length > 0 && config.target != "") { //if == 0, already filtered
            var currentVal = $(config.target).val();
            if ($(this).val() == config.defaultValue) {
                $(config.target).html("").prepend($(config.originalOptions).clone()).val(currentVal);
            } else...