├── .gitignore ├── CHANGELOG.md ├── Cakefile ├── LICENSE ├── README.md ├── jquery.dependent-selects.coffee └── jquery.dependent-selects.js /.gitignore: -------------------------------------------------------------------------------- 1 | index.html 2 | style.css 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # jQuery Dependent Selects Change Log 2 | 3 | ## Version 1.2.1 4 | 5 | - Fixes a display bug with placeholder selects and labels not displaying at the correct times 6 | 7 | ## Version 1.2.0 8 | 9 | - Allow placeholder selects for sub levels through a passed array of strings 10 | - Allow placeholder selects to be set through a data attribute on the select 11 | 12 | ## Version 1.1.0 13 | 14 | - Allow labels to be created for sub selects through a passed array of strings 15 | - Allow labels to be created from a data attribute on the select 16 | 17 | ## Version 1.0.4 18 | 19 | - Placeholder option now accepts an array of strings to be used at different levels 20 | 21 | ## Version 1.0.3 22 | 23 | - Fixes a bug where select name values were getting removed from all selects on the page 24 | - Fixes submission of automatically retained values (related to other bug ^) 25 | 26 | ## Version 1.0.2 27 | 28 | - Fixes a selected retention bug if the selected item was the first in a group or sub-group 29 | 30 | ## Version 1.0.1 31 | 32 | - Retains selected option upon instantiation (including correct nesting) 33 | -------------------------------------------------------------------------------- /Cakefile: -------------------------------------------------------------------------------- 1 | fs = require 'fs' 2 | {spawn, exec} = require 'child_process' 3 | 4 | js_file_name = 'jquery.dependent-selects.js' 5 | coffee_file_name = 'jquery.dependent-selects.coffee' 6 | 7 | compileJS = (options = {}) -> 8 | console.log "Compiling #{coffee_file_name}..." 9 | exec "coffee --compile --bare #{coffee_file_name}", (err) -> 10 | if err? 11 | console.log 'Error :', err 12 | 13 | task 'build', 'Build things', -> 14 | compileJS() 15 | 16 | task 'watch', 'Watch things', -> 17 | compileJS() 18 | fs.watch coffee_file_name, -> 19 | compileJS() 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Mark J Smith, Simpleweb 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject 9 | to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 18 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 19 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jQuery Dependent Selects 2 | 3 | ## Overview 4 | 5 | A jQuery plugin to allow multi-level select boxes that degrade gracefully. It only changes the markup so the styling is all down to you. 6 | 7 | ## Demo 8 | 9 | You can check out some demos [over here](http://markjs.github.io/jquery-dependent-selects/)! 10 | 11 | ## Usage 12 | 13 | Ensure you have jQuery included in your page and include the `jquery-dependent-selects.js` script by adding something like this to your page's `
`: 14 | 15 | *Note: Change the `src` value to match the location of the scripts.* 16 | 17 | ```html 18 | 19 | 20 | ``` 21 | 22 | Mark up your selects as you'd like them to work without JavaScript, ensuring their text displays a consistent separator. For example: 23 | 24 | ```html 25 | 37 | ``` 38 | 39 | Initiate the plugin on the selects you would like it to be activated: 40 | 41 | *Note: It's best for this be done after the DOM is ready. For more info see [this brief tutorial](http://bit.ly/TxePc).* 42 | 43 | ```javascript 44 | $('.dependent').dependentSelects(); 45 | ``` 46 | 47 | **Viola!** Your select boxes should now be dependently nested. 48 | 49 | ## Customisation 50 | 51 | There's some options you can pass into jQuery Dependent Selects when called. They are listed here showing the defaults: 52 | 53 | ```javascript 54 | $('.example-class').dependentSelects({ 55 | separator: ' > ', // String: The separator used to define the nesting in the option field's text 56 | placeholderOption: '', // String or array of strings: The text used for the sub select boxes' placeholder option. 57 | // If an array, the first 'sub' level will be the first array item, you should manually create 58 | // the top level's placeholder in the HTML. 59 | placeholderSelect: false, // Array of strings: The text used for placeholder select boxes for sub levels. 60 | class: false, // String: Add an extra class to all sub selects 61 | labels: false // Array of strings: The text used for the sub select boxes' labels. Label element is 62 | // inserted before sub select. 63 | }); 64 | ``` 65 | 66 | ## Contributing 67 | 68 | The plugin is written in CoffeeScript and a Cakefile is included with `build` and `watch` tasks. If you'd like to contribute, please write CoffeeScript and use the Cake tasks to compile it. 69 | 70 | 1. Fork project 71 | 2. Checkout to a new branch (named after the feature / change you're making) 72 | 3. Write code (as mentioned above) 73 | 4. Submit pull request 74 | 75 | ## License 76 | 77 | Licenced under MIT. Copyright Mark J Smith, Simpleweb. 78 | -------------------------------------------------------------------------------- /jquery.dependent-selects.coffee: -------------------------------------------------------------------------------- 1 | ### 2 | # jQuery Dependent Selects v1.2.2 3 | # Copyright 2012 Mark J Smith, Simpleweb 4 | # Details on http://github.com/simpleweb/jquery-dependent-selects 5 | ### 6 | 7 | (($) -> 8 | 9 | $.fn.dependentSelects = (options = {}) -> 10 | options = $.extend({ 11 | 'separator': ' > ' 12 | 'placeholderOption': '' 13 | 'placeholderSelect': false 14 | 'class': false 15 | 'labels': false 16 | }, options) 17 | 18 | createSelectId = -> 19 | # Yeah, it'll fall down if you have more than 1000 of these on a page. 20 | # But if you're doing that, then you should go outside and breathe some fresh air. 21 | int = parseInt(Math.random()*1000) 22 | if $("[data-dependent-id='#{int}']").length > 0 23 | createSelectId() 24 | else 25 | int 26 | 27 | splitName = ($name) -> 28 | array = $.map($name.split(options.separator), (valuePart) -> $.trim(valuePart)) 29 | i = 0 30 | for item in array 31 | if item == '' 32 | array.splice(i, 1) 33 | i-- 34 | i++ 35 | array 36 | 37 | splitOptionName = ($option) -> 38 | splitName($option.text()) 39 | 40 | 41 | 42 | placeholderSelectAtDepth = (depth, $select) -> 43 | depth-- 44 | placeholder = options.placeholderSelect 45 | if placeholder 46 | if placeholder == true 47 | placeholder = $select.data('dependent-select-placeholders') 48 | if typeof placeholder == 'object' 49 | if placeholder[depth] 50 | text = placeholder[depth] 51 | else 52 | text = placeholder[placeholder.length-1] 53 | else 54 | text = placeholder 55 | $("").attr({ 56 | 'data-dependent-depth': depth+1 57 | 'data-dependent-placeholder': true 58 | 'data-dependent-id': $select.attr('data-dependent-id') 59 | }) 60 | 61 | placeholderOptionAtDepth = (depth) -> 62 | depth-- 63 | placeholder = options.placeholderOption 64 | if typeof placeholder == 'object' 65 | if placeholder[depth] 66 | text = placeholder[depth] 67 | else 68 | text = placeholder[placeholder.length-1] 69 | else 70 | text = placeholder 71 | $("") 72 | 73 | labelAtDepth = (depth, $select) -> 74 | depth-- 75 | labels = options.labels 76 | if labels 77 | if labels == true 78 | labels = $select.data('dependent-labels') 79 | if labels[depth] 80 | labels[depth] 81 | else 82 | labels[labels.length-1] 83 | else 84 | false 85 | 86 | hideSelect = ($select) -> 87 | select_id = $select.attr('data-dependent-id') 88 | select_depth = $select.attr('data-dependent-depth') 89 | placeholder_select = $("select[data-dependent-placeholder][data-dependent-id='#{select_id}'][data-dependent-depth='#{select_depth}']") 90 | label = $("label[data-dependent-id='#{select_id}'][data-dependent-depth='#{select_depth}']").hide() 91 | if placeholder_select.length > 0 92 | placeholder_select.show() 93 | label.show() 94 | 95 | $select.hide() 96 | 97 | showSelect = ($select) -> 98 | select_id = $select.attr('data-dependent-id') 99 | select_depth = $select.attr('data-dependent-depth') 100 | placeholder_select = $("select[data-dependent-placeholder][data-dependent-id='#{select_id}'][data-dependent-depth='#{select_depth}']") 101 | label = $("label[data-dependent-id='#{select_id}'][data-dependent-depth='#{select_depth}']").show() 102 | if placeholder_select.length > 0 103 | placeholder_select.hide() 104 | 105 | $select.show() 106 | 107 | insertLabel = ($select, $parent) -> 108 | if label = labelAtDepth($select.attr('data-dependent-depth'), $select) 109 | select_id = $select.attr('data-dependent-id') 110 | select_depth = $select.attr('data-dependent-depth') 111 | $label = $("").attr({ 112 | 'data-dependent-id': select_id 113 | 'data-dependent-depth': select_depth 114 | }) 115 | unless $("label[data-dependent-id='#{select_id}'][data-dependent-depth='#{select_depth}']").length > 0 116 | if $parent 117 | $parent.after($label) 118 | else 119 | $select.before($label) 120 | 121 | insertPlaceholderSelect = ($select) -> 122 | if $placeholderSelect = placeholderSelectAtDepth($select.attr('data-dependent-depth'), $select) 123 | select_id = $select.attr('data-dependent-id') 124 | depth = $select.attr('data-dependent-depth') 125 | unless $("select[data-dependent-placeholder][data-dependent-id='#{select_id}'][data-dependent-depth='#{depth}']").length > 0 126 | $select.before($placeholderSelect) 127 | 128 | clearAllSelectsByParent = ($parent) -> 129 | $(".dependent-sub[data-dependent-id='#{$parent.attr('data-dependent-id')}']").each -> 130 | if parseInt($(@).attr('data-dependent-depth')) > parseInt($parent.attr('data-dependent-depth')) 131 | $(@).find('option:first').attr('selected', 'selected') 132 | hideSelect $(@) 133 | 134 | createNewSelect = (name, $select, depth) -> 135 | select_id = $select.attr('data-dependent-id') 136 | path = pathForOption($select, name) 137 | 138 | if ($currentSelect = $("select[data-dependent-path='#{path}'][data-dependent-id='#{select_id}']")).length > 0 139 | return $currentSelect 140 | 141 | $newSelect = $('') 142 | .attr('data-dependent-depth', depth) 143 | .attr('data-dependent-input-name', $select.attr('data-dependent-input-name')) 144 | .attr('data-dependent-id', select_id) 145 | .addClass(options.class) 146 | .append(placeholderOptionAtDepth(depth)) 147 | 148 | if options.labels == true 149 | $newSelect.attr('data-dependent-labels', $select.attr('data-dependent-labels')) 150 | 151 | if options.placeholderSelect == true 152 | $newSelect.attr('data-dependent-select-placeholders', $select.attr('data-dependent-select-placeholders')) 153 | 154 | if ($labels = $("label[data-dependent-id='#{select_id}'][data-dependent-depth='#{depth}']")).length > 0 155 | $newSelect.insertAfter($labels) 156 | else 157 | $newSelect.insertAfter($select) 158 | 159 | insertLabel($newSelect, $select) 160 | insertPlaceholderSelect($newSelect) 161 | hideSelect($newSelect) 162 | 163 | selectChange = ($select) -> 164 | $("select[data-dependent-id='#{$select.attr('data-dependent-id')}'][name]").removeAttr('name') 165 | valName = $select.find(':selected').html() 166 | val = $select.val() 167 | select_id = $select.attr('data-dependent-id') 168 | clearAllSelectsByParent($select) 169 | 170 | path = pathForOption($select, valName).replace("'", "\\'") 171 | if ($sub = $(".dependent-sub[data-dependent-path='#{path}'][data-dependent-id='#{select_id}']")).length > 0 172 | showSelect $sub 173 | $sub.attr('name', $select.attr('data-dependent-input-name')) 174 | else 175 | $select.attr('name', $select.attr('data-dependent-input-name')) 176 | 177 | selectedOption = ($select) -> 178 | $selectedOption = $select.find('option:selected') 179 | val = $selectedOption.val() 180 | unless val == '' or val == placeholderOptionAtDepth($select.attr('data-dependent-depth')).val() 181 | $select.attr('data-dependent-selected-id', val) 182 | 183 | findSelectParent = ($select) -> 184 | $selects = $("select[data-dependent-id='#{$select.attr('data-dependent-id')}']") 185 | $all_options = $selects.find('option') 186 | 187 | $selects.filter( -> 188 | vals = [] 189 | $(@).find('option').each -> 190 | vals.push $(@).attr('data-dependent-path') == $select.attr('data-dependent-path') 191 | $.inArray(true, vals) > -1 192 | ) 193 | 194 | pathForOption = ($select, $name) -> 195 | if $select.attr('data-dependent-depth') == '0' 196 | $name 197 | else 198 | $select.attr('data-dependent-path') + options.separator + $name 199 | 200 | selectPreSelected = ($select) -> 201 | if (selected_id = $select.attr('data-dependent-selected-id')) 202 | $selects = $("select[data-dependent-id='#{$select.attr('data-dependent-id')}']") 203 | $all_options = $selects.find('option') 204 | 205 | $selected_option = $all_options.filter("[value='#{selected_id}']") 206 | $selected_select = $selected_option.closest('select') 207 | 208 | $current_select = $selected_select 209 | current_option_text = $selected_option.html() 210 | 211 | for i in [(parseInt $selected_select.attr('data-dependent-depth'))..0] 212 | $current_select.find('option').each -> 213 | if $(@).html() == current_option_text 214 | $(@).attr('selected', 'selected') 215 | else 216 | $(@).removeAttr('selected') 217 | 218 | showSelect $current_select 219 | current_option_text = splitName($current_select.attr('data-dependent-path')).slice(-1)[0]; 220 | $current_select = findSelectParent($current_select) 221 | 222 | $selected_select.trigger('change') 223 | 224 | prepareSelect = ($select, depth, select_id) -> 225 | $select.attr('data-dependent-depth', depth).attr('data-dependent-id', select_id) 226 | $options = $select.children('option') 227 | $options.each -> 228 | $option = $(@) 229 | 230 | name = splitOptionName($option) 231 | val = $option.val() 232 | if name.length > 1 233 | # Create sub select 234 | $subSelect = createNewSelect(htmlEncode(name[0]), $select, depth + 1) 235 | path = pathForOption($select, name[0]) 236 | $subSelect.attr('data-dependent-path', path) 237 | # Copy option into sub select 238 | $newOption = $option.clone() 239 | $newOption.html($.trim(splitOptionName($newOption)[1..-1].join(options.separator))) 240 | $subSelect.append($newOption) 241 | 242 | # Change option to just parent location 243 | 244 | $option.val('').html(name[0]).attr('data-dependent-path', path) 245 | # Remove option if already one for that parent location 246 | $option.remove() if $options.parent().find("[data-dependent-path='#{path}']").length > 1 247 | 248 | prepareSelect($subSelect, depth + 1, select_id) 249 | 250 | name = $select.attr('name') 251 | 252 | selectChange($select) 253 | 254 | $select.off('change').on 'change', -> 255 | selectChange($select) 256 | 257 | htmlEncode = (value) -> 258 | $('').text(value).html(); 259 | 260 | # Loop through each of the selects the plugin is called on, and set them up! 261 | @each -> 262 | $select = $(@) 263 | $select.attr('data-dependent-input-name', $select.attr('name')) 264 | selectedOption($select) 265 | 266 | prepareSelect $select, 0, createSelectId() 267 | selectPreSelected($select) 268 | 269 | )(jQuery) 270 | -------------------------------------------------------------------------------- /jquery.dependent-selects.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.9.1 2 | 3 | /* 4 | * jQuery Dependent Selects v1.2.2 5 | * Copyright 2012 Mark J Smith, Simpleweb 6 | * Details on http://github.com/simpleweb/jquery-dependent-selects 7 | */ 8 | (function($) { 9 | return $.fn.dependentSelects = function(options) { 10 | var clearAllSelectsByParent, createNewSelect, createSelectId, findSelectParent, hideSelect, htmlEncode, insertLabel, insertPlaceholderSelect, labelAtDepth, pathForOption, placeholderOptionAtDepth, placeholderSelectAtDepth, prepareSelect, selectChange, selectPreSelected, selectedOption, showSelect, splitName, splitOptionName; 11 | if (options == null) { 12 | options = {}; 13 | } 14 | options = $.extend({ 15 | 'separator': ' > ', 16 | 'placeholderOption': '', 17 | 'placeholderSelect': false, 18 | 'class': false, 19 | 'labels': false 20 | }, options); 21 | createSelectId = function() { 22 | var int; 23 | int = parseInt(Math.random() * 1000); 24 | if ($("[data-dependent-id='" + int + "']").length > 0) { 25 | return createSelectId(); 26 | } else { 27 | return int; 28 | } 29 | }; 30 | splitName = function($name) { 31 | var array, i, item, j, len; 32 | array = $.map($name.split(options.separator), function(valuePart) { 33 | return $.trim(valuePart); 34 | }); 35 | i = 0; 36 | for (j = 0, len = array.length; j < len; j++) { 37 | item = array[j]; 38 | if (item === '') { 39 | array.splice(i, 1); 40 | i--; 41 | } 42 | i++; 43 | } 44 | return array; 45 | }; 46 | splitOptionName = function($option) { 47 | return splitName($option.text()); 48 | }; 49 | placeholderSelectAtDepth = function(depth, $select) { 50 | var placeholder, text; 51 | depth--; 52 | placeholder = options.placeholderSelect; 53 | if (placeholder) { 54 | if (placeholder === true) { 55 | placeholder = $select.data('dependent-select-placeholders'); 56 | } 57 | if (typeof placeholder === 'object') { 58 | if (placeholder[depth]) { 59 | text = placeholder[depth]; 60 | } else { 61 | text = placeholder[placeholder.length - 1]; 62 | } 63 | } else { 64 | text = placeholder; 65 | } 66 | return $("").attr({ 67 | 'data-dependent-depth': depth + 1, 68 | 'data-dependent-placeholder': true, 69 | 'data-dependent-id': $select.attr('data-dependent-id') 70 | }); 71 | } 72 | }; 73 | placeholderOptionAtDepth = function(depth) { 74 | var placeholder, text; 75 | depth--; 76 | placeholder = options.placeholderOption; 77 | if (typeof placeholder === 'object') { 78 | if (placeholder[depth]) { 79 | text = placeholder[depth]; 80 | } else { 81 | text = placeholder[placeholder.length - 1]; 82 | } 83 | } else { 84 | text = placeholder; 85 | } 86 | return $(""); 87 | }; 88 | labelAtDepth = function(depth, $select) { 89 | var labels; 90 | depth--; 91 | labels = options.labels; 92 | if (labels) { 93 | if (labels === true) { 94 | labels = $select.data('dependent-labels'); 95 | } 96 | if (labels[depth]) { 97 | return labels[depth]; 98 | } else { 99 | return labels[labels.length - 1]; 100 | } 101 | } else { 102 | return false; 103 | } 104 | }; 105 | hideSelect = function($select) { 106 | var label, placeholder_select, select_depth, select_id; 107 | select_id = $select.attr('data-dependent-id'); 108 | select_depth = $select.attr('data-dependent-depth'); 109 | placeholder_select = $("select[data-dependent-placeholder][data-dependent-id='" + select_id + "'][data-dependent-depth='" + select_depth + "']"); 110 | label = $("label[data-dependent-id='" + select_id + "'][data-dependent-depth='" + select_depth + "']").hide(); 111 | if (placeholder_select.length > 0) { 112 | placeholder_select.show(); 113 | label.show(); 114 | } 115 | return $select.hide(); 116 | }; 117 | showSelect = function($select) { 118 | var label, placeholder_select, select_depth, select_id; 119 | select_id = $select.attr('data-dependent-id'); 120 | select_depth = $select.attr('data-dependent-depth'); 121 | placeholder_select = $("select[data-dependent-placeholder][data-dependent-id='" + select_id + "'][data-dependent-depth='" + select_depth + "']"); 122 | label = $("label[data-dependent-id='" + select_id + "'][data-dependent-depth='" + select_depth + "']").show(); 123 | if (placeholder_select.length > 0) { 124 | placeholder_select.hide(); 125 | } 126 | return $select.show(); 127 | }; 128 | insertLabel = function($select, $parent) { 129 | var $label, label, select_depth, select_id; 130 | if (label = labelAtDepth($select.attr('data-dependent-depth'), $select)) { 131 | select_id = $select.attr('data-dependent-id'); 132 | select_depth = $select.attr('data-dependent-depth'); 133 | $label = $("").attr({ 134 | 'data-dependent-id': select_id, 135 | 'data-dependent-depth': select_depth 136 | }); 137 | if (!($("label[data-dependent-id='" + select_id + "'][data-dependent-depth='" + select_depth + "']").length > 0)) { 138 | if ($parent) { 139 | return $parent.after($label); 140 | } else { 141 | return $select.before($label); 142 | } 143 | } 144 | } 145 | }; 146 | insertPlaceholderSelect = function($select) { 147 | var $placeholderSelect, depth, select_id; 148 | if ($placeholderSelect = placeholderSelectAtDepth($select.attr('data-dependent-depth'), $select)) { 149 | select_id = $select.attr('data-dependent-id'); 150 | depth = $select.attr('data-dependent-depth'); 151 | if (!($("select[data-dependent-placeholder][data-dependent-id='" + select_id + "'][data-dependent-depth='" + depth + "']").length > 0)) { 152 | return $select.before($placeholderSelect); 153 | } 154 | } 155 | }; 156 | clearAllSelectsByParent = function($parent) { 157 | return $(".dependent-sub[data-dependent-id='" + ($parent.attr('data-dependent-id')) + "']").each(function() { 158 | if (parseInt($(this).attr('data-dependent-depth')) > parseInt($parent.attr('data-dependent-depth'))) { 159 | $(this).find('option:first').attr('selected', 'selected'); 160 | return hideSelect($(this)); 161 | } 162 | }); 163 | }; 164 | createNewSelect = function(name, $select, depth) { 165 | var $currentSelect, $labels, $newSelect, path, select_id; 166 | select_id = $select.attr('data-dependent-id'); 167 | path = pathForOption($select, name); 168 | if (($currentSelect = $("select[data-dependent-path='" + path + "'][data-dependent-id='" + select_id + "']")).length > 0) { 169 | return $currentSelect; 170 | } 171 | $newSelect = $('').attr('data-dependent-depth', depth).attr('data-dependent-input-name', $select.attr('data-dependent-input-name')).attr('data-dependent-id', select_id).addClass(options["class"]).append(placeholderOptionAtDepth(depth)); 172 | if (options.labels === true) { 173 | $newSelect.attr('data-dependent-labels', $select.attr('data-dependent-labels')); 174 | } 175 | if (options.placeholderSelect === true) { 176 | $newSelect.attr('data-dependent-select-placeholders', $select.attr('data-dependent-select-placeholders')); 177 | } 178 | if (($labels = $("label[data-dependent-id='" + select_id + "'][data-dependent-depth='" + depth + "']")).length > 0) { 179 | $newSelect.insertAfter($labels); 180 | } else { 181 | $newSelect.insertAfter($select); 182 | } 183 | insertLabel($newSelect, $select); 184 | insertPlaceholderSelect($newSelect); 185 | return hideSelect($newSelect); 186 | }; 187 | selectChange = function($select) { 188 | var $sub, path, select_id, val, valName; 189 | $("select[data-dependent-id='" + ($select.attr('data-dependent-id')) + "'][name]").removeAttr('name'); 190 | valName = $select.find(':selected').html(); 191 | val = $select.val(); 192 | select_id = $select.attr('data-dependent-id'); 193 | clearAllSelectsByParent($select); 194 | path = pathForOption($select, valName).replace("'", "\\'"); 195 | if (($sub = $(".dependent-sub[data-dependent-path='" + path + "'][data-dependent-id='" + select_id + "']")).length > 0) { 196 | showSelect($sub); 197 | return $sub.attr('name', $select.attr('data-dependent-input-name')); 198 | } else { 199 | return $select.attr('name', $select.attr('data-dependent-input-name')); 200 | } 201 | }; 202 | selectedOption = function($select) { 203 | var $selectedOption, val; 204 | $selectedOption = $select.find('option:selected'); 205 | val = $selectedOption.val(); 206 | if (!(val === '' || val === placeholderOptionAtDepth($select.attr('data-dependent-depth')).val())) { 207 | return $select.attr('data-dependent-selected-id', val); 208 | } 209 | }; 210 | findSelectParent = function($select) { 211 | var $all_options, $selects; 212 | $selects = $("select[data-dependent-id='" + ($select.attr('data-dependent-id')) + "']"); 213 | $all_options = $selects.find('option'); 214 | return $selects.filter(function() { 215 | var vals; 216 | vals = []; 217 | $(this).find('option').each(function() { 218 | return vals.push($(this).attr('data-dependent-path') === $select.attr('data-dependent-path')); 219 | }); 220 | return $.inArray(true, vals) > -1; 221 | }); 222 | }; 223 | pathForOption = function($select, $name) { 224 | if ($select.attr('data-dependent-depth') === '0') { 225 | return $name; 226 | } else { 227 | return $select.attr('data-dependent-path') + options.separator + $name; 228 | } 229 | }; 230 | selectPreSelected = function($select) { 231 | var $all_options, $current_select, $selected_option, $selected_select, $selects, current_option_text, i, j, ref, selected_id; 232 | if ((selected_id = $select.attr('data-dependent-selected-id'))) { 233 | $selects = $("select[data-dependent-id='" + ($select.attr('data-dependent-id')) + "']"); 234 | $all_options = $selects.find('option'); 235 | $selected_option = $all_options.filter("[value='" + selected_id + "']"); 236 | $selected_select = $selected_option.closest('select'); 237 | $current_select = $selected_select; 238 | current_option_text = $selected_option.html(); 239 | for (i = j = ref = parseInt($selected_select.attr('data-dependent-depth')); ref <= 0 ? j <= 0 : j >= 0; i = ref <= 0 ? ++j : --j) { 240 | $current_select.find('option').each(function() { 241 | if ($(this).html() === current_option_text) { 242 | return $(this).attr('selected', 'selected'); 243 | } else { 244 | return $(this).removeAttr('selected'); 245 | } 246 | }); 247 | showSelect($current_select); 248 | current_option_text = splitName($current_select.attr('data-dependent-path')).slice(-1)[0]; 249 | $current_select = findSelectParent($current_select); 250 | } 251 | return $selected_select.trigger('change'); 252 | } 253 | }; 254 | prepareSelect = function($select, depth, select_id) { 255 | var $options, name; 256 | $select.attr('data-dependent-depth', depth).attr('data-dependent-id', select_id); 257 | $options = $select.children('option'); 258 | $options.each(function() { 259 | var $newOption, $option, $subSelect, name, path, val; 260 | $option = $(this); 261 | name = splitOptionName($option); 262 | val = $option.val(); 263 | if (name.length > 1) { 264 | $subSelect = createNewSelect(htmlEncode(name[0]), $select, depth + 1); 265 | path = pathForOption($select, name[0]); 266 | $subSelect.attr('data-dependent-path', path); 267 | $newOption = $option.clone(); 268 | $newOption.html($.trim(splitOptionName($newOption).slice(1).join(options.separator))); 269 | $subSelect.append($newOption); 270 | $option.val('').html(name[0]).attr('data-dependent-path', path); 271 | if ($options.parent().find("[data-dependent-path='" + path + "']").length > 1) { 272 | $option.remove(); 273 | } 274 | return prepareSelect($subSelect, depth + 1, select_id); 275 | } 276 | }); 277 | name = $select.attr('name'); 278 | selectChange($select); 279 | return $select.off('change').on('change', function() { 280 | return selectChange($select); 281 | }); 282 | }; 283 | htmlEncode = function(value) { 284 | return $('').text(value).html(); 285 | }; 286 | return this.each(function() { 287 | var $select; 288 | $select = $(this); 289 | $select.attr('data-dependent-input-name', $select.attr('name')); 290 | selectedOption($select); 291 | prepareSelect($select, 0, createSelectId()); 292 | return selectPreSelected($select); 293 | }); 294 | }; 295 | })(jQuery); 296 | --------------------------------------------------------------------------------