432 lines
14 KiB
JavaScript
432 lines
14 KiB
JavaScript
|
/* 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,
|
||
|
"destroy": function(element) {
|
||
|
this.destroy(element);
|
||
|
},
|
||
|
"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.dropdownClass);
|
||
|
$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(val[i]);
|
||
|
}
|
||
|
if (selectors.length > 0) {
|
||
|
var $target = $dropdown.find(function() { return $.inArray($(this).data("value"), selectors) !== -1; });
|
||
|
$target.removeClass("selected");
|
||
|
methods._select($dropdown, $target);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 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);
|
||
|
|
||
|
});
|
||
|
|
||
|
$select.on("DOMNodeRemoved", function(e) {
|
||
|
var deletedValue = $(e.target).attr('value');
|
||
|
$ul.find("li").filter(function() { return $(this).data("value") === deletedValue; }).remove();
|
||
|
var $selected;
|
||
|
|
||
|
setTimeout(function () {
|
||
|
if ($select.find(":selected").length) {
|
||
|
$selected = $select.find(":selected").last();
|
||
|
}
|
||
|
else {
|
||
|
$selected = $select.find("option, li").first();
|
||
|
}
|
||
|
methods._select($dropdown, $selected);
|
||
|
}, 100);
|
||
|
|
||
|
});
|
||
|
|
||
|
// Update dropdown when using val, need to use .val("value").trigger("change");
|
||
|
$select.on("change", function(e) {
|
||
|
var $this = $(e.target);
|
||
|
|
||
|
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 {
|
||
|
var target = $select.find(":selected"),
|
||
|
values = $(this).val();
|
||
|
// Unselect all options
|
||
|
selectOptions.removeClass("selected");
|
||
|
// Select options
|
||
|
target.each(function () {
|
||
|
var selected = selectOptions.filter(function() { return $.inArray($(this).data("value"), values) !== -1; });
|
||
|
selected.addClass("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", $("body").hasClass("rtl") ? "top-right" : "top-left");
|
||
|
} else {
|
||
|
$ul.attr("placement", $("body").hasClass("rtl") ? "bottom-right" : "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);
|
||
|
if ($(e.target).hasClass('disabled')) {
|
||
|
return;
|
||
|
}
|
||
|
$input.removeClass("focus");
|
||
|
});
|
||
|
}
|
||
|
|
||
|
if (options.autoinit) {
|
||
|
$(document).on("DOMNodeInserted", function(e) {
|
||
|
var $this = $(e.target);
|
||
|
if (!$this.is("select")) {
|
||
|
$this = $this.find('select');
|
||
|
}
|
||
|
$this.each(function() {
|
||
|
if ($(this).is(options.autoinit)) {
|
||
|
initElement($(this));
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
|
||
|
// Loop trough elements
|
||
|
$(this).each(function() {
|
||
|
initElement($(this));
|
||
|
});
|
||
|
},
|
||
|
select: function(target) {
|
||
|
var $target = $(this).find(function() { return $(this).data("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 value = $(this).prop("tagName") === "OPTION" ? $(this).val() : $(this).data("value"),
|
||
|
$selected = $select.find("[value=\"" + 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) {
|
||
|
if ($target.hasClass("disabled")) {
|
||
|
return;
|
||
|
}
|
||
|
// 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 input
|
||
|
$input.val($target.text().trim());
|
||
|
var value = $target.prop("tagName") === "OPTION" ? $target.val() : $target.data("value");
|
||
|
// When val is set below on $select, it will fire change event,
|
||
|
// which ends up back here, make sure to not end up in an infinite loop.
|
||
|
// This is done last so text input is initialized on first load when condition is true.
|
||
|
if (value === $select.val()) {
|
||
|
return;
|
||
|
}
|
||
|
// Set the value to the native select
|
||
|
$select.val(value);
|
||
|
}
|
||
|
|
||
|
// 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.data("value"));
|
||
|
}
|
||
|
|
||
|
},
|
||
|
_addOption: function($ul, $this) {
|
||
|
// Create the option
|
||
|
var $option = $("<li></li>");
|
||
|
|
||
|
// Style the option
|
||
|
$option.addClass(this.options.optionClass);
|
||
|
|
||
|
// 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.data("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");
|
||
|
}
|
||
|
|
||
|
if ($this.prop("disabled")) {
|
||
|
$option.addClass("disabled");
|
||
|
}
|
||
|
|
||
|
// Append option to our dropdown
|
||
|
if ($ul.find(".dropdownjs-add").length) {
|
||
|
$ul.find(".dropdownjs-add").before($option);
|
||
|
} else {
|
||
|
$ul.append($option);
|
||
|
}
|
||
|
},
|
||
|
destroy: function($e) {
|
||
|
$($e).show().removeAttr('data-dropdownjs').next('.dropdownjs').remove();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
$.fn.dropdown = function(params) {
|
||
|
if( typeof methods[params] == 'function' ) methods[params](this);
|
||
|
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");
|
||
|
}
|
||
|
};
|
||
|
|
||
|
}));
|
||
|
|