Skip to content

Instantly share code, notes, and snippets.

@jayalfredprufrock
Created November 10, 2011 21:30
Show Gist options
  • Save jayalfredprufrock/1356298 to your computer and use it in GitHub Desktop.
Save jayalfredprufrock/1356298 to your computer and use it in GitHub Desktop.
jQuery plugin to turn a single delimited list of select dropdown options into a chained selection of dropdowns, degrading gracefully.
/**
* jquery.dynamicDropdown.js
* Creates a chained selection out of a single <select> box, degrading gracefully
*
* @author Andrew Smiley <jayalfredprufrock@gmail.com>
* @ground up rewrite of Sean "Kovik" Smith's <kovik@koviko.net> plugin of the same name
* @version 1.0
* @requires jQuery 1.6.2+
* @fileoverview
* Usage:
*
* Select the target <select> box and run dynamicDropdown().
*
* i.e.
* $("select").dynamicDropdown();
*
* You can also build a dynamic dropdown using the options to select a
* delimiter and/or a class name for the <select> boxes.
*
* i.e.
* $("select").dynamicDropdown({
* "delimiter" : " » ",
* "levels" : [
* {"markup" : "<label>Make</label>{dd}", "class" : "dropdown", "id" : "make", "disabled" : "false"},
* {"markup" : "<label>Model</label>{dd}"},
* {"markup" : "<label>Year</label>{dd}"}
* ]
* });
*
* The target <select> box must use a delimiter to separate the levels. The
* default delimiter is " » " but any can be used. Just be sure to set it in
* the options for dynamicDropdown().
*
* i.e.
* <select>
* <option value="1">Ford » Mustang » 2000</option>
* <option value="2">Ford » Mustang » 2005</option>
* <option value="3">Ford » Focus » 2010</option>
* <option value="4">Oldsmobile » Alero » 1993</option>
* </select>
*
*
* Note that, unlike the above example, the options do not have to have the
* same amount of levels in order to work. Also, unlike the above example,
* the value of the options does not need to be numeric.
*
* Options:
*
* delimiter:
* The delimiter that separates different levels of the drop down.
* The default delimiter is " » ".
*
* levels:
* An array of "level" objects with support for the following keys at each level:
* markup : the html to use when creating the select input at the corresponding level, use {dd} to indicate placement of input - default: {dd}
* class : the class to give to the select element at the corresponding level - default: false
* id : the id to give to the select element at the corresponding level - default: false
* disabled : whether the select element at the corresponding level should be disabled - default: false
*/
(function($)
{
$.fn.dynamicDropdown = function(options) {
var settings = {
"delimiter" : " » ",
"className" : "",
"levels" : [
{'markup':"{dd}",'class':false,'id':false,'disabled':false},
{'markup':"{dd}"}
]
};
$.extend(settings, options);
return $(this).each(function() {
//the original dropdown element
var mainDropdown = $(this);
var defaultSelection = false;
var levels = {};
//insert dropdown into markup, and finally place it in the DOM, attaching events, etc.
var insertSelectDropdown = function(dd, level, sibling, position){
var markup = settings.levels[level] && settings.levels[level].markup ? settings.levels[level].markup : '{dd}';
//to support markup both placing the dropdown within a container and without a container,
//its necessary to use a little silly dom magic
var container = $('<div>'+settings.levels[level].markup.replace('{dd}',$('<div></div>').append(dd.addClass('ddlevel-'+level)).html())+'</div>').children()['insert'+position](sibling);
var select = container.parent().find('select.ddlevel-'+level).removeClass('ddlevel-'+level);
if (settings.levels[level]['class']){
select.addClass(settings.levels[level]['class']);
}
if (settings.levels[level].id){
select.attr('id',settings.levels[level].id);
}
if (settings.levels[level].disabled){
select.prop('disabled','disabled');
}
return select.data('level',level).data('container',container).data('levels',dd.data('levels')).change(updateDropdowns);
}
//produce markup for select element
var buildSelectDropdown = function(options, selected) {
var select = $('<select></select>').data('levels',options);
// Add options
$.each(options,function(index,value){
var option = $('<option></option>').html(index);
if (typeof(value) != 'object'){
option.val(value);
}
if (selected && index == selected){
option.attr('selected','selected');
}
select.append(option);
});
return select;
};
//the event function that runs each time a select input value changes
var updateDropdowns = function(){
var current = $(this).children(':selected').html();
var options = $(this).data('levels')[current];
//a non-object means this is the end of the line, set the value
if (typeof(options) != 'object'){
mainDropdown.val($(this).val());
}
else {
//remove any dds after the one that just changed
var dd = $(this);
while (dd.data('next')){
dd = dd.data('next');
dd.data('container').detach();
}
var level = $(this).data('level') + 1;
//add new dds
$(this).data('next',insertSelectDropdown(buildSelectDropdown(options, defaultSelection[level]), level, $(this).data('container').last(), 'After').change());
}
};
//build levels from initial dropdown
mainDropdown.children().each(function() {
var options = $(this).html().split(settings.delimiter);
if ($(this).is(":selected")){
defaultSelection = options;
}
var level = levels;
for (var i=0; i < options.length; i++) {
if (!level[options[i]]){
//either an option is an object pointing to other objects/values,
//or some other type value, indicating that the user has made a selection
level[options[i]] = ((i+1)==options.length) ? $(this).val() : {};
}
level = level[options[i]];
}
});
//if no default selection, use first value
if (!defaultSelection){
defaultSelection = mainDropdown.children().first().html().split(settings.delimiter);
}
insertSelectDropdown(buildSelectDropdown(levels,defaultSelection[0]), 0, mainDropdown, 'Before').change();
//hide initial dropdown
}).hide();
};
})(jQuery);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment