;(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 = $("