123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410 |
- if (jQuery)(function($) {
- // render the html for a single option
- function renderOption(id, option) {
- var html = '<label><input type="checkbox" name="' + id + '[]" value="' + option.value + '"';
- if (option.selected) {
- html += ' checked="checked"';
- }
- html += ' />' + option.text + '</label>';
- return html;
- }
- // render the html for the options/optgroups
- function renderOptions(id, options, o) {
- var html = "";
- for (var i = 0; i < options.length; i++) {
- if (options[i].optgroup) {
- html += '<label class="optGroup">';
- if (o.optGroupSelectable) {
- html += '<input type="checkbox" class="optGroup" />' + options[i].optgroup;
- } else {
- html += options[i].optgroup;
- }
- html += '</label><div class="optGroupContainer">';
- html += renderOptions(id, options[i].options, o);
- html += '</div>';
- } else {
- html += renderOption(id, options[i]);
- }
- }
- return html;
- }
- // Building the actual options
- function buildOptions(options) {
- var multiSelect = $(this);
- var multiSelectOptions = multiSelect.next('.multiSelectOptions');
- var o = multiSelect.data("config");
- var callback = multiSelect.data("callback");
- // clear the existing options
- multiSelectOptions.html("");
- var html = "";
- // if we should have a select all option then add it
- if (o.selectAll) {
- html += '<label class="selectAll"><input type="checkbox" class="selectAll" />' + o.selectAllText + '</label>';
- }
- // generate the html for the new options
- html += renderOptions(multiSelect.attr('id'), options, o);
- multiSelectOptions.html(html);
- // variables needed to account for width changes due to a scrollbar
- var initialWidth = multiSelectOptions.width();
- var hasScrollbar = false;
- // set the height of the dropdown options
- if (multiSelectOptions.height() > o.listHeight) {
- multiSelectOptions.css("height", o.listHeight + 'px');
- hasScrollbar = true;
- } else {
- multiSelectOptions.css("height", '');
- }
- // if the there is a scrollbar and the browser did not already handle adjusting the width (i.e. Firefox) then we will need to manaually add the scrollbar width
- var scrollbarWidth = hasScrollbar && (initialWidth == multiSelectOptions.width()) ? 17 : 0;
- // set the width of the dropdown options
- if ((multiSelectOptions.width() + scrollbarWidth) < multiSelect.outerWidth()) {
- multiSelectOptions.css("width", multiSelect.outerWidth() - 2
- /*border*/
- + 'px');
- } else {
- multiSelectOptions.css("width", (multiSelectOptions.width() + scrollbarWidth) + 'px');
- }
- // Apply bgiframe if available on IE6
- if ($.fn.bgiframe) multiSelect.next('.multiSelectOptions').bgiframe({
- width: multiSelectOptions.width(),
- height: multiSelectOptions.height()
- });
- // Handle selectAll oncheck
- if (o.selectAll) {
- multiSelectOptions.find('INPUT.selectAll').on('click',
- function() {
- // update all the child checkboxes
- $(this).prop('checked') ? multiSelectOptions.find('INPUT:checkbox').not('.selectAll, .optGroup').prop('checked', true) : multiSelectOptions.find('INPUT:checkbox').not('.selectAll, .optGroup').prop('checked', false);
- multiSelectOptions.find('INPUT:checkbox').parent("LABEL").toggleClass('checked', $(this).prop('checked'));
- });
- }
- // Handle OptGroup oncheck
- if (o.optGroupSelectable) {
- multiSelectOptions.addClass('optGroupHasCheckboxes');
- multiSelectOptions.find('INPUT.optGroup').on('click',
- function() {
- // update all the child checkboxes
- $(this).prop('checked') ? $(this).parent().next().find('INPUT:checkbox').not('.selectAll, .optGroup').prop('checked', true) : $(this).parent().next().find('INPUT:checkbox').not('.selectAll, .optGroup').prop('checked', false);
- $(this).parent().next().find('INPUT:checkbox').parent("LABEL").toggleClass('checked', $(this).prop('checked'));
- });
- }
- // Handle all checkboxes
- multiSelectOptions.find('INPUT:checkbox').on('click',
- function() {
- // set the label checked class
- $(this).parent("LABEL").toggleClass('checked', $(this).prop('checked'));
- updateSelected.call(multiSelect);
- multiSelect.focus();
- if ($(this).parent().parent().hasClass('optGroupContainer')) {
- updateOptGroup.call(multiSelect, $(this).parent().parent().prev());
- }
- if (callback) {
- callback($(this));
- }
- });
- // Initial display
- multiSelectOptions.each(function() {
- $(this).find('INPUT:checked').parent().addClass('checked');
- });
- // Initialize selected and select all
- updateSelected.call(multiSelect);
- // Initialize optgroups
- if (o.optGroupSelectable) {
- multiSelectOptions.find('LABEL.optGroup').each(function() {
- updateOptGroup.call(multiSelect, $(this));
- });
- }
- // Handle hovers
- multiSelectOptions.find('LABEL:has(INPUT)').hover(function() {
- $(this).parent().find('LABEL').removeClass('hover');
- $(this).addClass('hover');
- },
- function() {
- $(this).parent().find('LABEL').removeClass('hover');
- });
- // Keyboard
- multiSelect.keydown(function(e) {
- var multiSelectOptions = $(this).next('.multiSelectOptions');
- // Is dropdown visible?
- if (multiSelectOptions.css('visibility') != 'hidden') {
- // Dropdown is visible
- // Tab
- if (e.keyCode == 9) {
- $(this).addClass('focus').trigger('click'); // esc, left, right - hide
- $(this).focus().next(':input').focus();
- return true;
- }
- // ESC, Left, Right
- if (e.keyCode == 27 || e.keyCode == 37 || e.keyCode == 39) {
- // Hide dropdown
- $(this).addClass('focus').trigger('click');
- }
- // Down || Up
- if (e.keyCode == 40 || e.keyCode == 38) {
- var allOptions = multiSelectOptions.find('LABEL');
- var oldHoverIndex = allOptions.index(allOptions.filter('.hover'));
- var newHoverIndex = -1;
- // if there is no current highlighted item then highlight the first item
- if (oldHoverIndex < 0) {
- // Default to first item
- multiSelectOptions.find('LABEL:first').addClass('hover');
- }
- // else if we are moving down and there is a next item then move
- else if (e.keyCode == 40 && oldHoverIndex < allOptions.length - 1) {
- newHoverIndex = oldHoverIndex + 1;
- }
- // else if we are moving up and there is a prev item then move
- else if (e.keyCode == 38 && oldHoverIndex > 0) {
- newHoverIndex = oldHoverIndex - 1;
- }
- if (newHoverIndex >= 0) {
- $(allOptions.get(oldHoverIndex)).removeClass('hover'); // remove the current highlight
- $(allOptions.get(newHoverIndex)).addClass('hover'); // add the new highlight
- // Adjust the viewport if necessary
- adjustViewPort(multiSelectOptions);
- }
- return false;
- }
- // Enter, Space
- if (e.keyCode == 13 || e.keyCode == 32) {
- var selectedCheckbox = multiSelectOptions.find('LABEL.hover INPUT:checkbox');
- // Set the checkbox (and label class)
- selectedCheckbox.prop('checked', !selectedCheckbox.attr('checked')).parent("LABEL").toggleClass('checked', selectedCheckbox.attr('checked'));
- // if the checkbox was the select all then set all the checkboxes
- if (selectedCheckbox.hasClass("selectAll")) {
- multiSelectOptions.find('INPUT:checkbox').prop('checked', selectedCheckbox.attr('checked')).parent("LABEL").addClass('checked').toggleClass('checked', selectedCheckbox.attr('checked'));
- }
- updateSelected.call(multiSelect);
- if (callback) callback($(this));
- return false;
- }
- // Any other standard keyboard character (try and match the first character of an option)
- if (e.keyCode >= 33 && e.keyCode <= 126) {
- // find the next matching item after the current hovered item
- var match = multiSelectOptions.find('LABEL:startsWith(' + String.fromCharCode(e.keyCode) + ')');
- var currentHoverIndex = match.index(match.filter('LABEL.hover'));
- // filter the set to any items after the current hovered item
- var afterHoverMatch = match.filter(function(index) {
- return index > currentHoverIndex;
- });
- // if there were no item after the current hovered item then try using the full search results (filtered to the first one)
- match = (afterHoverMatch.length >= 1 ? afterHoverMatch: match).filter("LABEL:first");
- if (match.length == 1) {
- // if we found a match then move the hover
- multiSelectOptions.find('LABEL.hover').removeClass('hover');
- match.addClass('hover');
- adjustViewPort(multiSelectOptions);
- }
- }
- } else {
- // Dropdown is not visible
- if (e.keyCode == 38 || e.keyCode == 40 || e.keyCode == 13 || e.keyCode == 32) { //up, down, enter, space - show
- // Show dropdown
- $(this).removeClass('focus').trigger('click');
- multiSelectOptions.find('LABEL:first').addClass('hover');
- return false;
- }
- // Tab key
- if (e.keyCode == 9) {
- // Shift focus to next INPUT element on page
- multiSelectOptions.next(':input').focus();
- return true;
- }
- }
- // Prevent enter key from submitting form
- if (e.keyCode == 13) return false;
- });
- }
- // Adjust the viewport if necessary
- function adjustViewPort(multiSelectOptions) {
- // check for and move down
- var selectionBottom = multiSelectOptions.find('LABEL.hover').position().top + multiSelectOptions.find('LABEL.hover').outerHeight();
- if (selectionBottom > multiSelectOptions.innerHeight()) {
- multiSelectOptions.scrollTop(multiSelectOptions.scrollTop() + selectionBottom - multiSelectOptions.innerHeight());
- }
- // check for and move up
- if (multiSelectOptions.find('LABEL.hover').position().top < 0) {
- multiSelectOptions.scrollTop(multiSelectOptions.scrollTop() + multiSelectOptions.find('LABEL.hover').position().top);
- }
- }
- // Update the optgroup checked status
- function updateOptGroup(optGroup) {
- var multiSelect = $(this);
- var o = multiSelect.data("config");
- // Determine if the optgroup should be checked
- if (o.optGroupSelectable) {
- var optGroupSelected = true;
- $(optGroup).next().find('INPUT:checkbox').each(function() {
- if (!$(this).prop('checked')) {
- optGroupSelected = false;
- return false;
- }
- });
- $(optGroup).find('INPUT.optGroup').prop('checked', optGroupSelected).parent("LABEL").toggleClass('checked', optGroupSelected);
- }
- }
- // Update the textbox with the total number of selected items, and determine select all
- function updateSelected() {
- var multiSelect = $(this);
- var multiSelectOptions = multiSelect.next('.multiSelectOptions');
- var o = multiSelect.data("config");
- var i = 0;
- var selectAll = true;
- var display = '';
- multiSelectOptions.find('INPUT:checkbox').not('.selectAll, .optGroup').each(function() {
- if ($(this).prop('checked')) {
- i++;
- display = display + $(this).parent().text() + ', ';
- } else selectAll = false;
- });
- // trim any end comma and surounding whitespace
- display = display.replace(/\s*\,\s*$/, '');
- if (i == 0) {
- multiSelect.find("span").html(o.noneSelected);
- } else {
- if (o.oneOrMoreSelected == '*') {
- multiSelect.find("span").html(display);
- multiSelect.attr("title", display);
- } else {
- multiSelect.find("span").html(o.oneOrMoreSelected.replace('%', i));
- }
- }
- // Determine if Select All should be checked
- if (o.selectAll) {
- multiSelectOptions.find('INPUT.selectAll').prop('checked', selectAll).parent("LABEL").toggleClass('checked', selectAll);
- }
- }
- $.extend($.fn, {
- multiSelect: function(o, callback) {
- // Default options
- if (!o) o = {};
- if (o.selectAll == undefined) o.selectAll = true;
- if (o.selectAllText == undefined) o.selectAllText = "Select All";
- if (o.noneSelected == undefined) o.noneSelected = 'Select options';
- if (o.oneOrMoreSelected == undefined) o.oneOrMoreSelected = '% selected';
- if (o.optGroupSelectable == undefined) o.optGroupSelectable = false;
- if (o.listHeight == undefined) o.listHeight = 150;
- // Initialize each multiSelect
- $(this).each(function() {
- var select = $(this);
- var html = '<a href="javascript:;" class="multiSelect"><span></span></a>';
- html += '<div class="multiSelectOptions" style="position: absolute; z-index: 99999; visibility: hidden;"></div>';
- $(select).after(html);
- var multiSelect = $(select).next('.multiSelect');
- var multiSelectOptions = multiSelect.next('.multiSelectOptions');
- // if the select object had a width defined then match the new multilsect to it
- multiSelect.find("span").css("width", $(select).width() + 'px');
- // Attach the config options to the multiselect
- multiSelect.data("config", o);
- // Attach the callback to the multiselect
- multiSelect.data("callback", callback);
- // Serialize the select options into json options
- var options = [];
- $(select).children().each(function() {
- if (this.tagName.toUpperCase() == 'OPTGROUP') {
- var suboptions = [];
- options.push({
- optgroup: $(this).attr('label'),
- options: suboptions
- });
- $(this).children('OPTION').each(function() {
- if ($(this).val() != '') {
- suboptions.push({
- text: $(this).html(),
- value: $(this).val(),
- selected: $(this).attr('selected')
- });
- }
- });
- } else if (this.tagName.toUpperCase() == 'OPTION') {
- if ($(this).val() != '') {
- options.push({
- text: $(this).html(),
- value: $(this).val(),
- selected: $(this).attr('selected')
- });
- }
- }
- });
- // Eliminate the original form element
- $(select).remove();
- // Add the id that was on the original select element to the new input
- multiSelect.attr("id", $(select).attr("id"));
- // Build the dropdown options
- buildOptions.call(multiSelect, options);
- // Events
- multiSelect.hover(function() {
- $(this).addClass('hover');
- },
- function() {
- $(this).removeClass('hover');
- }).click(function() {
- // Show/hide on click
- if ($(this).hasClass('active')) {
- $(this).multiSelectOptionsHide();
- } else {
- $(this).multiSelectOptionsShow();
- }
- return false;
- }).focus(function() {
- // So it can be styled with CSS
- $(this).addClass('focus');
- }).blur(function() {
- // So it can be styled with CSS
- $(this).removeClass('focus');
- });
- // Add an event listener to the window to close the multiselect if the user clicks off
- $(document).click(function(event) {
- // If somewhere outside of the multiselect was clicked then hide the multiselect
- if (! ($(event.target).parents().andSelf().is('.multiSelectOptions'))) {
- multiSelect.multiSelectOptionsHide();
- }
- });
- });
- },
- // Update the dropdown options
- multiSelectOptionsUpdate: function(options) {
- buildOptions.call($(this), options);
- },
- // Hide the dropdown
- multiSelectOptionsHide: function() {
- $(this).removeClass('active').removeClass('hover').next('.multiSelectOptions').css('visibility', 'hidden');
- },
- // Show the dropdown
- multiSelectOptionsShow: function() {
- var multiSelect = $(this);
- var multiSelectOptions = multiSelect.next('.multiSelectOptions');
- var o = multiSelect.data("config");
- // Hide any open option boxes
- $('.multiSelect').multiSelectOptionsHide();
- multiSelectOptions.find('LABEL').removeClass('hover');
- multiSelect.addClass('active').next('.multiSelectOptions').css('visibility', 'visible');
- multiSelect.focus();
- // reset the scroll to the top
- multiSelect.next('.multiSelectOptions').scrollTop(0);
- // Position it
- var offset = multiSelect.position();
- multiSelect.next('.multiSelectOptions').css({
- top: offset.top + $(this).outerHeight() + 'px'
- });
- multiSelect.next('.multiSelectOptions').css({
- left: offset.left + 'px'
- });
- },
- // get a coma-delimited list of selected values
- selectedValuesString: function() {
- var selectedValues = "";
- $(this).next('.multiSelectOptions').find('INPUT:checkbox:checked').not('.optGroup, .selectAll').each(function() {
- selectedValues += $(this).attr('value') + ",";
- });
- // trim any end comma and surounding whitespace
- return selectedValues.replace(/\s*\,\s*$/, '');
- }
- });
- // add a new ":startsWith" search filter
- $.expr[":"].startsWith = function(el, i, m) {
- var search = m[3];
- if (!search) return false;
- return eval("/^[/s]*" + search + "/i").test($(el).text());
- };
- })(jQuery);
|