;(function($) { /* * ui.dropdownchecklist * * Copyright (c) 2008-2010 Adrian Tosca, Copyright (c) 2010-2011 Ittrium LLC * Dual licensed under the MIT (MIT-LICENSE.txt) OR GPL (GPL-LICENSE.txt) licenses. * */ // The dropdown check list jQuery plugin transforms a regular select html element into a dropdown check list. $.widget("ui.dropdownchecklist", { // Some globlals // $.ui.dropdownchecklist.gLastOpened - keeps track of last opened dropdowncheck list so we can close it // $.ui.dropdownchecklist.gIDCounter - simple counter to provide a unique ID as needed version: function() { alert('DropDownCheckList v1.4'); }, // Creates the drop container that keeps the items and appends it to the document _appendDropContainer: function( controlItem ) { var wrapper = $("
"); // the container is wrapped in a div wrapper.addClass("ui-dropdownchecklist ui-dropdownchecklist-dropcontainer-wrapper"); wrapper.addClass("ui-widget"); // assign an id wrapper.attr("id",controlItem.attr("id") + '-ddw'); // initially positioned way off screen to prevent it from displaying // NOTE absolute position to enable width/height calculation wrapper.css({ position: 'absolute', left: "-33000px", top: "-33000px" }); var container = $(""); // the actual container container.addClass("ui-dropdownchecklist-dropcontainer ui-widget-content"); container.css("overflow-y", "auto"); wrapper.append(container); // insert the dropdown after the master control to try to keep the tab order intact // if you just add it to the end, tabbing out of the drop down takes focus off the page // @todo 22Sept2010 - check if size calculation is thrown off if the parent of the // selector is hidden. We may need to add it to the end of the document here, // calculate the size, and then move it back into proper position??? //$(document.body).append(wrapper); wrapper.insertAfter(controlItem); // flag that tells if the drop container is shown or not wrapper.isOpen = false; return wrapper; }, // Look for browser standard 'open' on a closed selector _isDropDownKeyShortcut: function(e,keycode) { return e.altKey && ($.ui.keyCode.DOWN == keycode);// Alt + Down Arrow }, // Look for key that will tell us to close the open dropdown _isDropDownCloseKey: function(e,keycode) { return ($.ui.keyCode.ESCAPE == keycode) || ($.ui.keyCode.ENTER == keycode); }, // Handler to change the active focus based on a keystroke, moving some count of // items from the element that has the current focus _keyFocusChange: function(target,delta,limitToItems) { // Find item with current focus var focusables = $(":focusable"); var index = focusables.index(target); if ( index >= 0 ) { index += delta; if ( limitToItems ) { // Bound change to list of input elements var allCheckboxes = this.dropWrapper.find("input:not([disabled])"); var firstIndex = focusables.index(allCheckboxes.get(0)); var lastIndex = focusables.index(allCheckboxes.get(allCheckboxes.length-1)); if ( index < firstIndex ) { index = lastIndex; } else if ( index > lastIndex ) { index = firstIndex; } } focusables.get(index).focus(); } }, // Look for navigation, open, close (wired to keyup) _handleKeyboard: function(e) { var self = this; var keyCode = (e.keyCode || e.which); if (!self.dropWrapper.isOpen && self._isDropDownKeyShortcut(e, keyCode)) { // Key command to open the dropdown e.stopImmediatePropagation(); self._toggleDropContainer(true); } else if (self.dropWrapper.isOpen && self._isDropDownCloseKey(e, keyCode)) { // Key command to close the dropdown (but we retain focus in the control) e.stopImmediatePropagation(); self._toggleDropContainer(false); self.controlSelector.focus(); } else if (self.dropWrapper.isOpen && (e.target.type == 'checkbox') && ((keyCode == $.ui.keyCode.DOWN) || (keyCode == $.ui.keyCode.UP)) ) { // Up/Down to cycle throught the open items e.stopImmediatePropagation(); self._keyFocusChange(e.target, (keyCode == $.ui.keyCode.DOWN) ? 1 : -1, true); } else if (self.dropWrapper.isOpen && (keyCode == $.ui.keyCode.TAB) ) { // I wanted to adjust normal 'tab' processing here, but research indicates // that TAB key processing is NOT a cancelable event. You have to use a timer // hack to pull the focus back to where you want it after browser tab // processing completes. Not going to work for us. //e.stopImmediatePropagation(); //self._keyFocusChange(e.target, (e.shiftKey) ? -1 : 1, true); } }, // Look for change of focus _handleFocus: function(e,focusIn,forDropdown) { var self = this; if (forDropdown && !self.dropWrapper.isOpen) { // if the focus changes when the control is NOT open, mark it to show where the focus is/is not e.stopImmediatePropagation(); if (focusIn) { self.controlSelector.addClass("ui-state-hover"); if ($.ui.dropdownchecklist.gLastOpened != null) { $.ui.dropdownchecklist.gLastOpened._toggleDropContainer( false ); } } else { self.controlSelector.removeClass("ui-state-hover"); } } else if (!forDropdown && !focusIn) { // The dropdown is open, and an item (NOT the dropdown) has just lost the focus. // we really need a reliable method to see who has the focus as we process the blur, // but that mechanism does not seem to exist. Instead we rely on a delay before // posting the blur, with a focus event cancelling it before the delay expires. if ( e != null ) { e.stopImmediatePropagation(); } self.controlSelector.removeClass("ui-state-hover"); self._toggleDropContainer( false ); } }, // Clear the pending change of focus, which keeps us 'in' the control _cancelBlur: function(e) { var self = this; if (self.blurringItem != null) { clearTimeout(self.blurringItem); self.blurringItem = null; } }, // Creates the control that will replace the source select and appends it to the document // The control resembles a regular select with single selection _appendControl: function() { var self = this, sourceSelect = this.sourceSelect, options = this.options; // the control is wrapped in a basic container // inline-block at this level seems to give us better size control var wrapper = $(""); wrapper.addClass("ui-dropdownchecklist ui-dropdownchecklist-selector-wrapper ui-widget"); wrapper.css( { display: "inline-block", cursor: "default", overflow: "hidden" } ); // assign an ID var baseID = sourceSelect.attr("id"); if ((baseID == null) || (baseID == "")) { baseID = "ddcl-" + $.ui.dropdownchecklist.gIDCounter++; } else { baseID = "ddcl-" + baseID; } wrapper.attr("id",baseID); // the actual control which you can style // inline-block needed to enable 'width' but has interesting problems cross browser var control = $(""); control.addClass("ui-dropdownchecklist-selector ui-state-default"); control.css( { display: "inline-block", overflow: "hidden", 'white-space': 'nowrap'} ); // Setting a tab index means we are interested in the tab sequence var tabIndex = sourceSelect.attr("tabIndex"); if ( tabIndex == null ) { tabIndex = 0; } else { tabIndex = parseInt(tabIndex); if ( tabIndex < 0 ) { tabIndex = 0; } } control.attr("tabIndex", tabIndex); control.keyup(function(e) {self._handleKeyboard(e);}); control.focus(function(e) {self._handleFocus(e,true,true);}); control.blur(function(e) {self._handleFocus(e,false,true);}); wrapper.append(control); // the optional icon (which is inherently a block) which we can float if (options.icon != null) { var iconPlacement = (options.icon.placement == null) ? "left" : options.icon.placement; var anIcon = $(""); anIcon.addClass("ui-icon"); anIcon.addClass( (options.icon.toOpen != null) ? options.icon.toOpen : "ui-icon-triangle-1-e"); anIcon.css({ 'float': iconPlacement }); control.append(anIcon); } // the text container keeps the control text that is built from the selected (checked) items // inline-block needed to prevent long text from wrapping to next line when icon is active var textContainer = $(""); textContainer.addClass("ui-dropdownchecklist-text"); textContainer.css( { display: "inline-block", 'white-space': "nowrap", overflow: "hidden" } ); control.append(textContainer); // add the hover styles to the control wrapper.hover( function() { if (!self.disabled) { control.addClass("ui-state-hover"); } } , function() { if (!self.disabled) { control.removeClass("ui-state-hover"); } } ); // clicking on the control toggles the drop container wrapper.click(function(event) { if (!self.disabled) { event.stopImmediatePropagation(); self._toggleDropContainer( !self.dropWrapper.isOpen ); } }); wrapper.insertAfter(sourceSelect); // Watch for a window resize and adjust the control if open $(window).resize(function() { if (!self.disabled && self.dropWrapper.isOpen) { // Reopen yourself to get the position right self._toggleDropContainer(true); } }); return wrapper; }, // Creates a drop item that coresponds to an option element in the source select _createDropItem: function(index, tabIndex, value, text, optCss, checked, disabled, indent) { var self = this, options = this.options, sourceSelect = this.sourceSelect, controlWrapper = this.controlWrapper; // the item contains a div that contains a checkbox input and a lable for the text // the div var item = $(""); item.addClass("ui-dropdownchecklist-item"); item.css({'white-space': "nowrap"}); var checkedString = checked ? ' checked="checked"' : ''; var classString = disabled ? ' class="inactive"' : ' class="active"'; // generated id must be a bit unique to keep from colliding var idBase = controlWrapper.attr("id"); var id = idBase + '-i' + index; var checkBox; // all items start out disabled to keep them out of the tab order if (self.isMultiple) { // the checkbox checkBox = $(''); } else { // the radiobutton checkBox = $(''); } checkBox = checkBox.attr("index", index).val(value); item.append(checkBox); // the text var label = $(""); label.addClass("ui-dropdownchecklist-text"); if ( optCss != null ) label.attr('style',optCss); label.css({ cursor: "default" }); label.html(text); if (indent) { item.addClass("ui-dropdownchecklist-indent"); } item.addClass("ui-state-default"); if (disabled) { item.addClass("ui-state-disabled"); } label.click(function(e) {e.stopImmediatePropagation();}); item.append(label); // active items display themselves with hover item.hover( function(e) { var anItem = $(this); if (!anItem.hasClass("ui-state-disabled")) { anItem.addClass("ui-state-hover"); } } , function(e) { var anItem = $(this); anItem.removeClass("ui-state-hover"); } ); // clicking on the checkbox synchronizes the source select checkBox.click(function(e) { var aCheckBox = $(this); e.stopImmediatePropagation(); if (aCheckBox.hasClass("active") ) { // Active checkboxes take active action var callback = self.options.onItemClick; if ($.isFunction(callback)) try { callback.call(self,aCheckBox,sourceSelect.get(0)); } catch (ex) { // reject the change on any error aCheckBox.prop("checked",!aCheckBox.prop("checked")); self._syncSelected(aCheckBox); return; } self._syncSelected(aCheckBox); self.sourceSelect.trigger("change", 'ddcl_internal'); if (!self.isMultiple && options.closeRadioOnClick) { self._toggleDropContainer(false); } } }); // we are interested in the focus leaving the check box // but we need to detect the focus leaving one check box but // entering another. There is no reliable way to detect who // received the focus on a blur, so post the blur in the future, // knowing we will cancel it if we capture the focus in a timely manner // 23Sept2010 - unfortunately, IE 7+ and Chrome like to post a blur // event to the current item with focus when the user // clicks in the scroll bar. So if you have a scrollable // dropdown with focus on an item, clicking in the scroll // will close the drop down. // I have no solution for blur processing at this time. /********* var timerFunction = function(){ // I had a hell of a time getting setTimeout to fire this, do not try to // define it within the blur function try { self._handleFocus(null,false,false); } catch(ex){ alert('timer failed: '+ex);} }; checkBox.blur(function(e) { self.blurringItem = setTimeout( timerFunction, 200 ); }); checkBox.focus(function(e) {self._cancelBlur();}); **********/ // check/uncheck the item on clicks on the entire item div item.click(function(e) { var anItem = $(this); e.stopImmediatePropagation(); if (!anItem.hasClass("ui-state-disabled") ) { // check/uncheck the underlying control var aCheckBox = anItem.find("input"); var checked = aCheckBox.prop("checked"); aCheckBox.prop("checked", !checked); var callback = self.options.onItemClick; if ($.isFunction(callback)) try { callback.call(self,aCheckBox,sourceSelect.get(0)); } catch (ex) { // reject the change on any error aCheckBox.prop("checked",checked); self._syncSelected(aCheckBox); return; } self._syncSelected(aCheckBox); self.sourceSelect.trigger("change", 'ddcl_internal'); if (!checked && !self.isMultiple && options.closeRadioOnClick) { self._toggleDropContainer(false); } } else { // retain the focus even if disabled anItem.focus(); self._cancelBlur(); } }); // do not let the focus wander around item.focus(function(e) { var anItem = $(this); e.stopImmediatePropagation(); }); item.keyup(function(e) {self._handleKeyboard(e);}); return item; }, _createGroupItem: function(text,disabled) { var self = this; var group = $(""); group.addClass("ui-dropdownchecklist-group ui-widget-header"); if (disabled) { group.addClass("ui-state-disabled"); } group.css({'white-space': "nowrap"}); var label = $(""); label.addClass("ui-dropdownchecklist-text"); label.css( { cursor: "default" }); label.text(text); group.append(label); // anything interesting when you click the group??? group.click(function(e) { var aGroup= $(this); e.stopImmediatePropagation(); // retain the focus even if no action is taken aGroup.focus(); self._cancelBlur(); }); // do not let the focus wander around group.focus(function(e) { var aGroup = $(this); e.stopImmediatePropagation(); }); return group; }, _createCloseItem: function(text) { var self = this; var closeItem = $(""); closeItem.addClass("ui-state-default ui-dropdownchecklist-close ui-dropdownchecklist-item"); closeItem.css({'white-space': 'nowrap', 'text-align': 'right'}); var label = $(""); label.addClass("ui-dropdownchecklist-text"); label.css( { cursor: "default" }); label.html(text); closeItem.append(label); // close the control on click closeItem.click(function(e) { var aGroup= $(this); e.stopImmediatePropagation(); // retain the focus even if no action is taken aGroup.focus(); self._toggleDropContainer( false ); }); closeItem.hover( function(e) { $(this).addClass("ui-state-hover"); } , function(e) { $(this).removeClass("ui-state-hover"); } ); // do not let the focus wander around closeItem.focus(function(e) { var aGroup = $(this); e.stopImmediatePropagation(); }); return closeItem; }, // Creates the drop items and appends them to the drop container // Also calculates the size needed by the drop container and returns it _appendItems: function() { var self = this, config = this.options, sourceSelect = this.sourceSelect, dropWrapper = this.dropWrapper; var dropContainerDiv = dropWrapper.find(".ui-dropdownchecklist-dropcontainer"); sourceSelect.children().each(function(index) { // when the select has groups var opt = $(this); if (opt.is("option")) { self._appendOption(opt, dropContainerDiv, index, false, false); } else if (opt.is("optgroup")) { var disabled = opt.prop("disabled"); var text = opt.attr("label"); if (text != "") { var group = self._createGroupItem(text,disabled); dropContainerDiv.append(group); } self._appendOptions(opt, dropContainerDiv, index, true, disabled); } }); if ( config.explicitClose != null ) { var closeItem = self._createCloseItem(config.explicitClose); dropContainerDiv.append(closeItem); } var divWidth = dropContainerDiv.outerWidth(); var divHeight = dropContainerDiv.outerHeight(); return { width: divWidth, height: divHeight }; }, _appendOptions: function(parent, container, parentIndex, indent, forceDisabled) { var self = this; parent.children("option").each(function(index) { var option = $(this); var childIndex = (parentIndex + "." + index); self._appendOption(option, container, childIndex, indent, forceDisabled); }); }, _appendOption: function(option, container, index, indent, forceDisabled) { var self = this; // Note that the browsers destroy any html structure within the OPTION var text = option.html(); if ( (text != null) && (text != '') ) { var value = option.val(); var optCss = option.attr('style'); var selected = option.prop("selected"); var disabled = (forceDisabled || option.prop("disabled")); // Use the same tab index as the selector replacement var tabIndex = self.controlSelector.attr("tabindex"); var item = self._createDropItem(index, tabIndex, value, text, optCss, selected, disabled, indent); container.append(item); } }, // Synchronizes the items checked and the source select // When firstItemChecksAll option is active also synchronizes the checked items // senderCheckbox parameters is the checkbox input that generated the synchronization _syncSelected: function(senderCheckbox) { var self = this, options = this.options, sourceSelect = this.sourceSelect, dropWrapper = this.dropWrapper; var selectOptions = sourceSelect.get(0).options; var allCheckboxes = dropWrapper.find("input.active"); if (options.firstItemChecksAll == 'exclusive') { if ((senderCheckbox == null) && $(selectOptions[0]).prop("selected") ) { // Initialization call with first item active allCheckboxes.prop("checked", false); $(allCheckboxes[0]).prop("checked", true); } else if ((senderCheckbox != null) && (senderCheckbox.attr("index") == 0)) { // Action on the first, so all other checkboxes NOT active var firstIsActive = senderCheckbox.prop("checked"); allCheckboxes.prop("checked", false); $(allCheckboxes[0]).prop("checked", firstIsActive); } else { // check the first checkbox if all the other checkboxes are checked var allChecked = true; var firstCheckbox = null; allCheckboxes.each(function(index) { if (index > 0) { var checked = $(this).prop("checked"); if (!checked) { allChecked = false; } } else { firstCheckbox = $(this); } }); if ( firstCheckbox != null ) { if ( allChecked ) { // when all are checked, only the first left checked allCheckboxes.prop("checked", false); } firstCheckbox.prop("checked", allChecked ); } } } else if (options.firstItemChecksAll) { if ((senderCheckbox == null) && $(selectOptions[0]).prop("selected") ) { // Initialization call with first item active so force all to be active allCheckboxes.prop("checked", true); } else if ((senderCheckbox != null) && (senderCheckbox.attr("index") == 0)) { // Check all checkboxes if the first one is checked allCheckboxes.prop("checked", senderCheckbox.prop("checked")); } else { // check the first checkbox if all the other checkboxes are checked var allChecked = true; var firstCheckbox = null; allCheckboxes.each(function(index) { if (index > 0) { var checked = $(this).prop("checked"); if (!checked) { allChecked = false; } } else { firstCheckbox = $(this); } }); if ( firstCheckbox != null ) { firstCheckbox.prop("checked", allChecked ); } } } // do the actual synch with the source select var empties = 0; allCheckboxes = dropWrapper.find("input"); allCheckboxes.each(function(index) { var anOption = $(selectOptions[index + empties]); var optionText = anOption.html(); if ( (optionText == null) || (optionText == '') ) { empties += 1; anOption = $(selectOptions[index + empties]); } anOption.prop("selected", $(this).prop("checked")); }); // update the text shown in the control self._updateControlText(); // Ensure the focus stays pointing where the user is working if ( senderCheckbox != null) { senderCheckbox.focus(); } }, _sourceSelectChangeHandler: function(event) { var self = this, dropWrapper = this.dropWrapper; dropWrapper.find("input").val(self.sourceSelect.val()); // update the text shown in the control self._updateControlText(); }, // Updates the text shown in the control depending on the checked (selected) items _updateControlText: function() { var self = this, sourceSelect = this.sourceSelect, options = this.options, controlWrapper = this.controlWrapper; var firstOption = sourceSelect.find("option:first"); var selectOptions = sourceSelect.find("option"); var text = self._formatText(selectOptions, options.firstItemChecksAll, firstOption); var controlLabel = controlWrapper.find(".ui-dropdownchecklist-text"); controlLabel.html(text); // the attribute needs naked text, not html controlLabel.attr("title", controlLabel.text()); }, // Formats the text that is shown in the control _formatText: function(selectOptions, firstItemChecksAll, firstOption) { var text; if ( $.isFunction(this.options.textFormatFunction) ) { // let the callback do the formatting, but do not allow it to fail try { text = this.options.textFormatFunction(selectOptions); } catch(ex) { alert( 'textFormatFunction failed: ' + ex ); } } else if (firstItemChecksAll && (firstOption != null) && firstOption.prop("selected")) { // just set the text from the first item text = firstOption.html(); } else { // concatenate the text from the checked items text = ""; selectOptions.each(function() { if ($(this).prop("selected")) { if ( text != "" ) { text += ", "; } /* NOTE use of .html versus .text, which can screw up ampersands for IE */ var optCss = $(this).attr('style'); var tempspan = $(''); tempspan.html( $(this).html() ); if ( optCss == null ) { text += tempspan.html(); } else { tempspan.attr('style',optCss); text += $("").append(tempspan).html(); } } }); if ( text == "" ) { text = (this.options.emptyText != null) ? this.options.emptyText : " "; } } return text; }, // Shows and hides the drop container _toggleDropContainer: function( makeOpen ) { var self = this; // hides the last shown drop container var hide = function(instance) { if ((instance != null) && instance.dropWrapper.isOpen ){ instance.dropWrapper.isOpen = false; $.ui.dropdownchecklist.gLastOpened = null; var config = instance.options; instance.dropWrapper.css({ top: "-33000px", left: "-33000px" }); var aControl = instance.controlSelector; aControl.removeClass("ui-state-active"); aControl.removeClass("ui-state-hover"); var anIcon = instance.controlWrapper.find(".ui-icon"); if ( anIcon.length > 0 ) { anIcon.removeClass( (config.icon.toClose != null) ? config.icon.toClose : "ui-icon-triangle-1-s"); anIcon.addClass( (config.icon.toOpen != null) ? config.icon.toOpen : "ui-icon-triangle-1-e"); } $(document).unbind("click", hide); // keep the items out of the tab order by disabling them instance.dropWrapper.find("input.active").prop("disabled",true); // the following blur just does not fire??? because it is hidden??? because it does not have focus??? //instance.sourceSelect.trigger("blur"); //instance.sourceSelect.triggerHandler("blur"); if($.isFunction(config.onComplete)) { try { config.onComplete.call(instance,instance.sourceSelect.get(0)); } catch(ex) { alert( 'callback failed: ' + ex ); }} } }; // shows the given drop container instance var show = function(instance) { if ( !instance.dropWrapper.isOpen ) { instance.dropWrapper.isOpen = true; $.ui.dropdownchecklist.gLastOpened = instance; var config = instance.options; /**** Issue127 (and the like) to correct positioning when parent element is relative **** This positioning only worked with simple, non-relative parent position instance.dropWrapper.css({ top: instance.controlWrapper.offset().top + instance.controlWrapper.outerHeight() + "px", left: instance.controlWrapper.offset().left + "px" }); ****/ if ((config.positionHow == null) || (config.positionHow == 'absolute')) { /** Floats above subsequent content, but does NOT scroll */ instance.dropWrapper.css({ position: 'absolute' , top: instance.controlWrapper.position().top + instance.controlWrapper.outerHeight() + "px" , left: instance.controlWrapper.position().left + "px" }); } else if (config.positionHow == 'relative') { /** Scrolls with the parent but does NOT float above subsequent content */ instance.dropWrapper.css({ position: 'relative' , top: "0px" , left: "0px" }); } var zIndex = 0; if (config.zIndex == null) { var ancestorsZIndexes = instance.controlWrapper.parents().map( function() { var zIndex = $(this).css("z-index"); return isNaN(zIndex) ? 0 : zIndex; } ).get(); var parentZIndex = Math.max.apply(Math, ancestorsZIndexes); if ( parentZIndex >= 0) zIndex = parentZIndex+1; } else { /* Explicit set from the optins */ zIndex = parseInt(config.zIndex); } if (zIndex > 0) { instance.dropWrapper.css( { 'z-index': zIndex } ); } var aControl = instance.controlSelector; aControl.addClass("ui-state-active"); aControl.removeClass("ui-state-hover"); var anIcon = instance.controlWrapper.find(".ui-icon"); if ( anIcon.length > 0 ) { anIcon.removeClass( (config.icon.toOpen != null) ? config.icon.toOpen : "ui-icon-triangle-1-e"); anIcon.addClass( (config.icon.toClose != null) ? config.icon.toClose : "ui-icon-triangle-1-s"); } $(document).bind("click", function(e) {hide(instance);} ); // insert the items back into the tab order by enabling all active ones var activeItems = instance.dropWrapper.find("input.active"); activeItems.prop("disabled",false); // we want the focus on the first active input item var firstActiveItem = activeItems.get(0); if ( firstActiveItem != null ) { firstActiveItem.focus(); } } }; if ( makeOpen ) { hide($.ui.dropdownchecklist.gLastOpened); show(self); } else { hide(self); } }, // Set the size of the control and of the drop container _setSize: function(dropCalculatedSize) { var options = this.options, dropWrapper = this.dropWrapper, controlWrapper = this.controlWrapper; // use the width from config options if set, otherwise set the same width as the drop container var controlWidth = dropCalculatedSize.width; if (options.width != null) { controlWidth = parseInt(options.width); } else if (options.minWidth != null) { var minWidth = parseInt(options.minWidth); // if the width is too small (usually when there are no items) set a minimum width if (controlWidth < minWidth) { controlWidth = minWidth; } } var control = this.controlSelector; control.css({ width: controlWidth + "px" }); // if we size the text, then Firefox places icons to the right properly // and we do not wrap on long lines var controlText = control.find(".ui-dropdownchecklist-text"); var controlIcon = control.find(".ui-icon"); if ( controlIcon != null ) { // Must be an inner/outer/border problem, but IE6 needs an extra bit of space, // otherwise you can get text pushed down into a second line when icons are active controlWidth -= (controlIcon.outerWidth() + 4); controlText.css( { width: controlWidth + "px" } ); } // Account for padding, borders, etc controlWidth = controlWrapper.outerWidth(); // the drop container height can be set from options var maxDropHeight = (options.maxDropHeight != null) ? parseInt(options.maxDropHeight) : -1; var dropHeight = ((maxDropHeight > 0) && (dropCalculatedSize.height > maxDropHeight)) ? maxDropHeight : dropCalculatedSize.height; // ensure the drop container is not less than the control width (would be ugly) var dropWidth = dropCalculatedSize.width < controlWidth ? controlWidth : dropCalculatedSize.width; $(dropWrapper).css({ height: dropHeight + "px", width: dropWidth + "px" }); dropWrapper.find(".ui-dropdownchecklist-dropcontainer").css({ height: dropHeight + "px" }); }, // Initializes the plugin _init: function() { var self = this, options = this.options; if ( $.ui.dropdownchecklist.gIDCounter == null) { $.ui.dropdownchecklist.gIDCounter = 1; } // item blurring relies on a cancelable timer self.blurringItem = null; // sourceSelect is the select on which the plugin is applied var sourceSelect = self.element; self.initialDisplay = sourceSelect.css("display"); sourceSelect.css("display", "none"); self.initialMultiple = sourceSelect.prop("multiple"); self.isMultiple = self.initialMultiple; if (options.forceMultiple != null) { self.isMultiple = options.forceMultiple; } sourceSelect.prop("multiple", true); self.sourceSelect = sourceSelect; // append the control that resembles a single selection select var controlWrapper = self._appendControl(); self.controlWrapper = controlWrapper; self.controlSelector = controlWrapper.find(".ui-dropdownchecklist-selector"); // create the drop container where the items are shown var dropWrapper = self._appendDropContainer(controlWrapper); self.dropWrapper = dropWrapper; // append the items from the source select element var dropCalculatedSize = self._appendItems(); // updates the text shown in the control self._updateControlText(controlWrapper, dropWrapper, sourceSelect); // set the sizes of control and drop container self._setSize(dropCalculatedSize); // look for possible auto-check needed on first item if ( options.firstItemChecksAll ) { self._syncSelected(null); } // BGIFrame for IE6 if (options.bgiframe && typeof self.dropWrapper.bgiframe == "function") { self.dropWrapper.bgiframe(); } // listen for change events on the source select element // ensure we avoid processing internally triggered changes self.sourceSelect.change(function(event, eventName) { if (eventName != 'ddcl_internal') { self._sourceSelectChangeHandler(event); } }); }, // Refresh the disable and check state from the underlying control _refreshOption: function(item,disabled,selected) { var aParent = item.parent(); // account for enabled/disabled if ( disabled ) { item.prop("disabled",true); item.removeClass("active"); item.addClass("inactive"); aParent.addClass("ui-state-disabled"); } else { item.prop("disabled",false); item.removeClass("inactive"); item.addClass("active"); aParent.removeClass("ui-state-disabled"); } // adjust the checkbox state item.prop("checked",selected); }, _refreshGroup: function(group,disabled) { if ( disabled ) { group.addClass("ui-state-disabled"); } else { group.removeClass("ui-state-disabled"); } }, // External command to explicitly close the dropdown close: function() { this._toggleDropContainer(false); }, // External command to refresh the ddcl from the underlying selector refresh: function() { var self = this, sourceSelect = this.sourceSelect, dropWrapper = this.dropWrapper; var allCheckBoxes = dropWrapper.find("input"); var allGroups = dropWrapper.find(".ui-dropdownchecklist-group"); var groupCount = 0; var optionCount = 0; sourceSelect.children().each(function(index) { var opt = $(this); var disabled = opt.prop("disabled"); if (opt.is("option")) { var selected = opt.prop("selected"); var anItem = $(allCheckBoxes[optionCount]); self._refreshOption(anItem, disabled, selected); optionCount += 1; } else if (opt.is("optgroup")) { var text = opt.attr("label"); if (text != "") { var aGroup = $(allGroups[groupCount]); self._refreshGroup(aGroup, disabled); groupCount += 1; } opt.children("option").each(function() { var subopt = $(this); var subdisabled = (disabled || subopt.prop("disabled")); var selected = subopt.prop("selected"); var subItem = $(allCheckBoxes[optionCount]); self._refreshOption(subItem, subdisabled, selected ); optionCount += 1; }); } }); // sync will handle firstItemChecksAll and updateControlText self._syncSelected(null); }, // External command to enable the ddcl control enable: function() { this.controlSelector.removeClass("ui-state-disabled"); this.disabled = false; }, // External command to disable the ddcl control disable: function() { this.controlSelector.addClass("ui-state-disabled"); this.disabled = true; }, // External command to destroy all traces of the ddcl control destroy: function() { $.Widget.prototype.destroy.apply(this, arguments); this.sourceSelect.css("display", this.initialDisplay); this.sourceSelect.prop("multiple", this.initialMultiple); this.controlWrapper.unbind().remove(); this.dropWrapper.remove(); } }); $.extend($.ui.dropdownchecklist, { defaults: { width: null , maxDropHeight: null , firstItemChecksAll: false , closeRadioOnClick: false , minWidth: 50 , positionHow: 'absolute' , bgiframe: false , explicitClose: null } }); })(jQuery);