jquery.dropdown.js (12234B)
1 /* globals jQuery, window, document */ 2 3 (function (factory) { 4 if (typeof define === 'function' && define.amd) { 5 // AMD. Register as an anonymous module. 6 define(['jquery'], factory); 7 } else if (typeof exports === 'object') { 8 // Node/CommonJS 9 module.exports = factory(require('jquery')); 10 } else { 11 // Browser globals 12 factory(jQuery); 13 } 14 }(function($) { 15 16 var methods = { 17 options : { 18 "optionClass": "", 19 "dropdownClass": "", 20 "autoinit": false, 21 "callback": false, 22 "onSelected": false, 23 "dynamicOptLabel": "Add a new option..." 24 }, 25 init: function(options) { 26 27 // Apply user options if user has defined some 28 if (options) { 29 options = $.extend(methods.options, options); 30 } else { 31 options = methods.options; 32 } 33 34 function initElement($select) { 35 // Don't do anything if this is not a select or if this select was already initialized 36 if ($select.data("dropdownjs") || !$select.is("select")) { 37 return; 38 } 39 40 // Is it a multi select? 41 var multi = $select.attr("multiple"); 42 43 // Does it allow to create new options dynamically? 44 var dynamicOptions = $select.attr("data-dynamic-opts"), 45 $dynamicInput = $(); 46 47 // Create the dropdown wrapper 48 var $dropdown = $("<div></div>"); 49 $dropdown.addClass("dropdownjs").addClass(options.dropdownStyle); 50 $dropdown.data("select", $select); 51 52 // Create the fake input used as "select" element and cache it as $input 53 var $input = $("<input type=text readonly class=fakeinput>"); 54 if ($.material) { $input.data("mdproc", true); } 55 // Append it to the dropdown wrapper 56 $dropdown.append($input); 57 58 // Create the UL that will be used as dropdown and cache it AS $ul 59 var $ul = $("<ul></ul>"); 60 $ul.data("select", $select); 61 62 // Append it to the dropdown 63 $dropdown.append($ul); 64 65 // Transfer the placeholder attribute 66 $input.attr("placeholder", $select.attr("placeholder")); 67 68 // Loop trough options and transfer them to the dropdown menu 69 $select.find("option").each(function() { 70 // Cache $(this) 71 var $this = $(this); 72 methods._addOption($ul, $this); 73 74 }); 75 76 // If this select allows dynamic options add the widget 77 if (dynamicOptions) { 78 $dynamicInput = $("<li class=dropdownjs-add></li>"); 79 $dynamicInput.append("<input>"); 80 $dynamicInput.find("input").attr("placeholder", options.dynamicOptLabel); 81 $ul.append($dynamicInput); 82 } 83 84 85 86 // Cache the dropdown options 87 var selectOptions = $dropdown.find("li"); 88 89 // If is a single select, selected the first one or the last with selected attribute 90 if (!multi) { 91 var $selected; 92 if ($select.find(":selected").length) { 93 $selected = $select.find(":selected").last(); 94 } 95 else { 96 $selected = $select.find("option, li").first(); 97 // $selected = $select.find("option").first(); 98 } 99 methods._select($dropdown, $selected); 100 } else { 101 var selectors = [], val = $select.val() 102 for (var i in val) { 103 selectors.push('li[value=' + val[i] + ']') 104 } 105 if (selectors.length > 0) { 106 methods._select($dropdown, $dropdown.find(selectors.join(','))); 107 } 108 } 109 110 // Transfer the classes of the select to the input dropdown 111 $input.addClass($select[0].className); 112 113 // Hide the old and ugly select 114 $select.hide().attr("data-dropdownjs", true); 115 116 // Bring to life our awesome dropdownjs 117 $select.after($dropdown); 118 119 // Call the callback 120 if (options.callback) { 121 options.callback($dropdown); 122 } 123 124 //---------------------------------------// 125 // DROPDOWN EVENTS // 126 //---------------------------------------// 127 128 // On click, set the clicked one as selected 129 $ul.on("click", "li:not(.dropdownjs-add)", function(e) { 130 methods._select($dropdown, $(this)); 131 // trigger change event, if declared on the original selector 132 $select.change(); 133 }); 134 $ul.on("keydown", "li:not(.dropdownjs-add)", function(e) { 135 if (e.which === 27) { 136 $(".dropdownjs > ul > li").attr("tabindex", -1); 137 return $input.removeClass("focus").blur(); 138 } 139 if (e.which === 32 && !$(e.target).is("input")) { 140 methods._select($dropdown, $(this)); 141 return false; 142 } 143 }); 144 145 $ul.on("focus", "li:not(.dropdownjs-add)", function() { 146 if ($select.is(":disabled")) { 147 return; 148 } 149 $input.addClass("focus"); 150 }); 151 152 // Add new options when the widget is used 153 if (dynamicOptions && dynamicOptions.length) { 154 $dynamicInput.on("keydown", function(e) { 155 if(e.which !== 13) return; 156 var $option = $("<option>"), 157 val = $dynamicInput.find("input").val(); 158 $dynamicInput.find("input").val(""); 159 160 $option.attr("value", val); 161 $option.text(val); 162 $select.append($option); 163 164 }); 165 } 166 167 // Listen for new added options and update dropdown if needed 168 $select.on("DOMNodeInserted", function(e) { 169 var $this = $(e.target); 170 if (!$this.val().length) return; 171 172 methods._addOption($ul, $this); 173 $ul.find("li").not(".dropdownjs-add").attr("tabindex", 0); 174 175 }); 176 177 // Update dropdown when using val, need to use .val("value").trigger("change"); 178 $select.on("change", function(e) { 179 var $this = $(e.target); 180 if (!$this.val().length) return; 181 182 if (!multi) { 183 var $selected; 184 if ($select.find(":selected").length) { 185 $selected = $select.find(":selected").last(); 186 } 187 else { 188 $selected = $select.find("option, li").first(); 189 } 190 methods._select($dropdown, $selected); 191 } else { 192 methods._select($dropdown, $select.find(":selected")); 193 } 194 }); 195 196 // Used to make the dropdown menu more dropdown-ish 197 $input.on("click focus", function(e) { 198 e.stopPropagation(); 199 if ($select.is(":disabled")) { 200 return; 201 } 202 $(".dropdownjs > ul > li").attr("tabindex", -1); 203 $(".dropdownjs > input").not($(this)).removeClass("focus").blur(); 204 205 $(".dropdownjs > ul > li").not(".dropdownjs-add").attr("tabindex", 0); 206 207 // Set height of the dropdown 208 var coords = { 209 top: $(this).offset().top - $(document).scrollTop(), 210 left: $(this).offset().left - $(document).scrollLeft(), 211 bottom: $(window).height() - ($(this).offset().top - $(document).scrollTop()), 212 right: $(window).width() - ($(this).offset().left - $(document).scrollLeft()) 213 }; 214 215 var height = coords.bottom; 216 217 // Decide if place the dropdown below or above the input 218 if (height < 200 && coords.top > coords.bottom) { 219 height = coords.top; 220 $ul.attr("placement", "top-left"); 221 } else { 222 $ul.attr("placement", "bottom-left"); 223 } 224 225 $(this).next("ul").css("max-height", height - 20); 226 $(this).addClass("focus"); 227 }); 228 // Close every dropdown on click outside 229 $(document).on("click", function(e) { 230 231 // Don't close the multi dropdown if user is clicking inside it 232 if (multi && $(e.target).parents(".dropdownjs").length) return; 233 234 // Don't close the dropdown if user is clicking inside the dynamic-opts widget 235 if ($(e.target).parents(".dropdownjs-add").length || $(e.target).is(".dropdownjs-add")) return; 236 237 // Close opened dropdowns 238 $(".dropdownjs > ul > li").attr("tabindex", -1); 239 $input.removeClass("focus"); 240 }); 241 } 242 243 if (options.autoinit) { 244 $(document).on("DOMNodeInserted", function(e) { 245 var $this = $(e.target); 246 if (!$this.is("select")) { 247 $this = $this.find('select'); 248 } 249 if ($this.is(options.autoinit)) { 250 initElement($this); 251 } 252 }); 253 } 254 255 // Loop trough elements 256 $(this).each(function() { 257 initElement($(this)); 258 }); 259 }, 260 select: function(target) { 261 var $target = $(this).find("[value=\"" + target + "\"]"); 262 methods._select($(this), $target); 263 }, 264 _select: function($dropdown, $target) { 265 if ($target.is(".dropdownjs-add")) return; 266 267 // Get dropdown's elements 268 var $select = $dropdown.data("select"), 269 $input = $dropdown.find("input.fakeinput"); 270 // Is it a multi select? 271 var multi = $select.attr("multiple"); 272 273 // Cache the dropdown options 274 var selectOptions = $dropdown.find("li"); 275 276 // Behavior for multiple select 277 if (multi) { 278 // Toggle option state 279 $target.toggleClass("selected"); 280 // Toggle selection of the clicked option in native select 281 $target.each(function(){ 282 var $selected = $select.find("[value=\"" + $(this).attr("value") + "\"]"); 283 $selected.prop("selected", $(this).hasClass("selected")); 284 }) 285 // Add or remove the value from the input 286 var text = []; 287 selectOptions.each(function() { 288 if ($(this).hasClass("selected")) { 289 text.push($(this).text()); 290 } 291 }); 292 $input.val(text.join(", ")); 293 } 294 295 // Behavior for single select 296 if (!multi) { 297 // Unselect options except the one that will be selected 298 selectOptions.not($target).removeClass("selected"); 299 // Select the selected option 300 $target.addClass("selected"); 301 // Set the value to the native select 302 $select.val($target.attr("value")); 303 // Set the value to the input 304 $input.val($target.text().trim()); 305 } 306 307 // This is used only if Material Design for Bootstrap is selected 308 if ($.material) { 309 if ($input.val().trim()) { 310 $select.removeClass("empty"); 311 } else { 312 $select.addClass("empty"); 313 } 314 } 315 316 // Call the callback 317 if (this.options.onSelected) { 318 this.options.onSelected($target.attr("value")); 319 } 320 321 }, 322 _addOption: function($ul, $this) { 323 // Create the option 324 var $option = $("<li></li>"); 325 326 // Style the option 327 $option.addClass(this.options.optionStyle); 328 329 // If the option has some text then transfer it 330 if ($this.text()) { 331 $option.text($this.text()); 332 } 333 // Otherwise set the empty label and set it as an empty option 334 else { 335 $option.html(" "); 336 } 337 // Set the value of the option 338 $option.attr("value", $this.val()); 339 340 // Will user be able to remove this option? 341 if ($ul.data("select").attr("data-dynamic-opts")) { 342 $option.append("<span class=close></span>"); 343 $option.find(".close").on("click", function() { 344 $option.remove(); 345 $this.remove(); 346 }); 347 } 348 349 // Ss it selected? 350 if ($this.prop("selected")) { 351 $option.attr("selected", true); 352 } 353 354 // Append option to our dropdown 355 if ($ul.find(".dropdownjs-add").length) { 356 $ul.find(".dropdownjs-add").before($option); 357 } else { 358 $ul.append($option); 359 } 360 } 361 }; 362 363 $.fn.dropdown = function(params) { 364 if (methods[params]) { 365 return methods[params].apply(this, Array.prototype.slice.call(arguments,1)); 366 } else if (typeof params === "object" | !params) { 367 return methods.init.apply(this, arguments); 368 } else { 369 $.error("Method " + params + " does not exists on jQuery.dropdown"); 370 } 371 }; 372 373 }));