/* globals jQuery, window, document */ (function (factory) { if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module. define(['jquery'], factory); } else if (typeof exports === 'object') { // Node/CommonJS module.exports = factory(require('jquery')); } else { // Browser globals factory(jQuery); } }(function($) { var methods = { options : { "optionClass": "", "dropdownClass": "", "autoinit": false, "callback": false, "onSelected": false, "dynamicOptLabel": "Add a new option..." }, init: function(options) { // Apply user options if user has defined some if (options) { options = $.extend(methods.options, options); } else { options = methods.options; } function initElement($select) { // Don't do anything if this is not a select or if this select was already initialized if ($select.data("dropdownjs") || !$select.is("select")) { return; } // Is it a multi select? var multi = $select.attr("multiple"); // Does it allow to create new options dynamically? var dynamicOptions = $select.attr("data-dynamic-opts"), $dynamicInput = $(); // Create the dropdown wrapper var $dropdown = $("<div></div>"); $dropdown.addClass("dropdownjs").addClass(options.dropdownStyle); $dropdown.data("select", $select); // Create the fake input used as "select" element and cache it as $input var $input = $("<input type=text readonly class=fakeinput>"); if ($.material) { $input.data("mdproc", true); } // Append it to the dropdown wrapper $dropdown.append($input); // Create the UL that will be used as dropdown and cache it AS $ul var $ul = $("<ul></ul>"); $ul.data("select", $select); // Append it to the dropdown $dropdown.append($ul); // Transfer the placeholder attribute $input.attr("placeholder", $select.attr("placeholder")); // Loop trough options and transfer them to the dropdown menu $select.find("option").each(function() { // Cache $(this) var $this = $(this); methods._addOption($ul, $this); }); // If this select allows dynamic options add the widget if (dynamicOptions) { $dynamicInput = $("<li class=dropdownjs-add></li>"); $dynamicInput.append("<input>"); $dynamicInput.find("input").attr("placeholder", options.dynamicOptLabel); $ul.append($dynamicInput); } // Cache the dropdown options var selectOptions = $dropdown.find("li"); // If is a single select, selected the first one or the last with selected attribute if (!multi) { var $selected; if ($select.find(":selected").length) { $selected = $select.find(":selected").last(); } else { $selected = $select.find("option, li").first(); // $selected = $select.find("option").first(); } methods._select($dropdown, $selected); } else { var selectors = [], val = $select.val() for (var i in val) { selectors.push('li[value=' + val[i] + ']') } if (selectors.length > 0) { methods._select($dropdown, $dropdown.find(selectors.join(','))); } } // Transfer the classes of the select to the input dropdown $input.addClass($select[0].className); // Hide the old and ugly select $select.hide().attr("data-dropdownjs", true); // Bring to life our awesome dropdownjs $select.after($dropdown); // Call the callback if (options.callback) { options.callback($dropdown); } //---------------------------------------// // DROPDOWN EVENTS // //---------------------------------------// // On click, set the clicked one as selected $ul.on("click", "li:not(.dropdownjs-add)", function(e) { methods._select($dropdown, $(this)); // trigger change event, if declared on the original selector $select.change(); }); $ul.on("keydown", "li:not(.dropdownjs-add)", function(e) { if (e.which === 27) { $(".dropdownjs > ul > li").attr("tabindex", -1); return $input.removeClass("focus").blur(); } if (e.which === 32 && !$(e.target).is("input")) { methods._select($dropdown, $(this)); return false; } }); $ul.on("focus", "li:not(.dropdownjs-add)", function() { if ($select.is(":disabled")) { return; } $input.addClass("focus"); }); // Add new options when the widget is used if (dynamicOptions && dynamicOptions.length) { $dynamicInput.on("keydown", function(e) { if(e.which !== 13) return; var $option = $("<option>"), val = $dynamicInput.find("input").val(); $dynamicInput.find("input").val(""); $option.attr("value", val); $option.text(val); $select.append($option); }); } // Listen for new added options and update dropdown if needed $select.on("DOMNodeInserted", function(e) { var $this = $(e.target); if (!$this.val().length) return; methods._addOption($ul, $this); $ul.find("li").not(".dropdownjs-add").attr("tabindex", 0); }); // Update dropdown when using val, need to use .val("value").trigger("change"); $select.on("change", function(e) { var $this = $(e.target); if (!$this.val().length) return; if (!multi) { var $selected; if ($select.find(":selected").length) { $selected = $select.find(":selected").last(); } else { $selected = $select.find("option, li").first(); } methods._select($dropdown, $selected); } else { methods._select($dropdown, $select.find(":selected")); } }); // Used to make the dropdown menu more dropdown-ish $input.on("click focus", function(e) { e.stopPropagation(); if ($select.is(":disabled")) { return; } $(".dropdownjs > ul > li").attr("tabindex", -1); $(".dropdownjs > input").not($(this)).removeClass("focus").blur(); $(".dropdownjs > ul > li").not(".dropdownjs-add").attr("tabindex", 0); // Set height of the dropdown var coords = { top: $(this).offset().top - $(document).scrollTop(), left: $(this).offset().left - $(document).scrollLeft(), bottom: $(window).height() - ($(this).offset().top - $(document).scrollTop()), right: $(window).width() - ($(this).offset().left - $(document).scrollLeft()) }; var height = coords.bottom; // Decide if place the dropdown below or above the input if (height < 200 && coords.top > coords.bottom) { height = coords.top; $ul.attr("placement", "top-left"); } else { $ul.attr("placement", "bottom-left"); } $(this).next("ul").css("max-height", height - 20); $(this).addClass("focus"); }); // Close every dropdown on click outside $(document).on("click", function(e) { // Don't close the multi dropdown if user is clicking inside it if (multi && $(e.target).parents(".dropdownjs").length) return; // Don't close the dropdown if user is clicking inside the dynamic-opts widget if ($(e.target).parents(".dropdownjs-add").length || $(e.target).is(".dropdownjs-add")) return; // Close opened dropdowns $(".dropdownjs > ul > li").attr("tabindex", -1); $input.removeClass("focus"); }); } if (options.autoinit) { $(document).on("DOMNodeInserted", function(e) { var $this = $(e.target); if (!$this.is("select")) { $this = $this.find('select'); } if ($this.is(options.autoinit)) { $this.each(function() { initElement($(this)); }); } }); } // Loop trough elements $(this).each(function() { initElement($(this)); }); }, select: function(target) { var $target = $(this).find("[value=\"" + target + "\"]"); methods._select($(this), $target); }, _select: function($dropdown, $target) { if ($target.is(".dropdownjs-add")) return; // Get dropdown's elements var $select = $dropdown.data("select"), $input = $dropdown.find("input.fakeinput"); // Is it a multi select? var multi = $select.attr("multiple"); // Cache the dropdown options var selectOptions = $dropdown.find("li"); // Behavior for multiple select if (multi) { // Toggle option state $target.toggleClass("selected"); // Toggle selection of the clicked option in native select $target.each(function(){ var $selected = $select.find("[value=\"" + $(this).attr("value") + "\"]"); $selected.prop("selected", $(this).hasClass("selected")); }) // Add or remove the value from the input var text = []; selectOptions.each(function() { if ($(this).hasClass("selected")) { text.push($(this).text()); } }); $input.val(text.join(", ")); } // Behavior for single select if (!multi) { // Unselect options except the one that will be selected if ($target.is("li")) { selectOptions.not($target).removeClass("selected"); } // Select the selected option $target.addClass("selected"); // Set the value to the native select $select.val($target.attr("value")); // Set the value to the input $input.val($target.text().trim()); } // This is used only if Material Design for Bootstrap is selected if ($.material) { if ($input.val().trim()) { $select.removeClass("empty"); } else { $select.addClass("empty"); } } // Call the callback if (this.options.onSelected) { this.options.onSelected($target.attr("value")); } }, _addOption: function($ul, $this) { // Create the option var $option = $("<li></li>"); // Style the option $option.addClass(this.options.optionStyle); // If the option has some text then transfer it if ($this.text()) { $option.text($this.text()); } // Otherwise set the empty label and set it as an empty option else { $option.html(" "); } // Set the value of the option $option.attr("value", $this.val()); // Will user be able to remove this option? if ($ul.data("select").attr("data-dynamic-opts")) { $option.append("<span class=close></span>"); $option.find(".close").on("click", function() { $option.remove(); $this.remove(); }); } // Ss it selected? if ($this.prop("selected")) { $option.attr("selected", true); $option.addClass("selected"); } // Append option to our dropdown if ($ul.find(".dropdownjs-add").length) { $ul.find(".dropdownjs-add").before($option); } else { $ul.append($option); } } }; $.fn.dropdown = function(params) { if (methods[params]) { return methods[params].apply(this, Array.prototype.slice.call(arguments,1)); } else if (typeof params === "object" | !params) { return methods.init.apply(this, arguments); } else { $.error("Method " + params + " does not exists on jQuery.dropdown"); } }; }));