Created
November 10, 2011 21:30
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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