/* * jQuery selectBox - A cosmetic, styleable replacement for SELECT elements * * Licensed under the MIT license: http://opensource.org/licenses/MIT * Autor: Marc J. Schmidt * v1.2.0 * * https://github.com/marcj/jquery-selectBox */ (function( $ ) { var SelectBox = this.SelectBox = function ( select, options ) { if ( select instanceof jQuery ) { if ( select.length > 0 ) { select = select[ 0 ]; } else { return; } } this.typeTimer = null; this.typeSearch = ''; this.isMac = navigator.platform.match( /mac/i ); options = 'object' === typeof options ? options : {}; this.selectElement = select; // Disable for iOS devices (their native controls are more suitable for a touch device) if ( !options.mobile && navigator.userAgent.match( /iPad|iPhone|Android|IEMobile|BlackBerry/i ) ) { return false; } // Element must be a select control if ( 'select' !== select.tagName.toLowerCase() ) { return false; } this.init( options ); } /** * @type {String} */ SelectBox.prototype.version = '1.2.0'; /** * @param {Object} options * * @returns {Boolean} */ SelectBox.prototype.init = function ( options ) { var select = $( this.selectElement ); if ( select.data( 'selectBox-control' ) ) { return false; } var control = $( '' ) , inline = select.attr( 'multiple' ) || parseInt( select.attr('size') ) > 1 , settings = options || {} , tabIndex = parseInt( select.prop( 'tabindex' ) ) || 0 , self = this; control .width( select.outerWidth() ) .addClass( select.attr('class') ) .attr( 'title', select.attr( 'title' ) || '' ) .attr( 'tabindex', tabIndex ) .css( 'display', 'inline-block' ) .bind( 'focus.selectBox', function () { if ( this !== document.activeElement && document.body !== document.activeElement ) { $( document.activeElement ).blur(); } if ( control.hasClass( 'selectBox-active' ) ) { return; } control.addClass( 'selectBox-active' ); select.trigger( 'focus' ); }) .bind( 'blur.selectBox', function () { if ( !control.hasClass( 'selectBox-active' ) ) { return; } control.removeClass( 'selectBox-active' ); select.trigger( 'blur' ); }); if ( !$( window ).data( 'selectBox-bindings' ) ) { $ (window ) .data( 'selectBox-bindings', true ) .bind( 'scroll.selectBox', ( settings.hideOnWindowScroll ) ? this.hideMenus : $.noop ) .bind( 'resize.selectBox', this.hideMenus ); } if ( select.attr( 'disabled' ) ) { control.addClass( 'selectBox-disabled' ); } // Focus on control when label is clicked select.bind( 'click.selectBox', function ( event ) { control.focus(); event.preventDefault(); }); // Generate control if ( inline ) { // Inline controls options = this.getOptions( 'inline' ); control .append( options ) .data( 'selectBox-options', options ).addClass( 'selectBox-inline selectBox-menuShowing' ) .bind( 'keydown.selectBox', function ( event ) { self.handleKeyDown( event ); }) .bind( 'keypress.selectBox',function ( event ) { self.handleKeyPress( event ); }) .bind( 'mousedown.selectBox',function ( event ) { if ( 1 !== event.which ) { return; } if ( $( event.target ).is( 'A.selectBox-inline' ) ) { event.preventDefault(); } if ( !control.hasClass( 'selectBox-focus' ) ) { control.focus(); } }) .insertAfter( select ); // Auto-height based on size attribute if ( !select[ 0 ].style.height ) { var size = select.attr( 'size' ) ? parseInt( select.attr( 'size' ) ) : 5; // Draw a dummy control off-screen, measure, and remove it var tmp = control .clone() .removeAttr( 'id' ) .css( { position: 'absolute', top: '-9999em' }) .show() .appendTo( 'body' ); tmp.find( '.selectBox-options' ).html( '
  • \u00A0
  • ' ); var optionHeight = parseInt( tmp.find( '.selectBox-options A:first' ).html( ' ' ).outerHeight() ); tmp.remove(); control.height( optionHeight * size ); } this.disableSelection( control ); } else { // Dropdown controls var label = $( '' ), arrow = $( '' ); // Update label label.attr( 'class', this.getLabelClass() ).text( this.getLabelText() ); options = this.getOptions( 'dropdown' ); options.appendTo( 'BODY' ); control .data( 'selectBox-options', options ) .addClass( 'selectBox-dropdown' ) .append( label ) .append( arrow ) .bind( 'mousedown.selectBox', function ( event ) { if ( 1 === event.which ) { if ( control.hasClass( 'selectBox-menuShowing' ) ) { self.hideMenus(); } else { event.stopPropagation(); // Webkit fix to prevent premature selection of options options .data( 'selectBox-down-at-x', event.screenX ) .data( 'selectBox-down-at-y', event.screenY ); self.showMenu(); } } }) .bind( 'keydown.selectBox', function ( event ) { self.handleKeyDown( event ); }) .bind( 'keypress.selectBox', function ( event ) { self.handleKeyPress( event ); }) .bind( 'open.selectBox',function ( event, triggerData ) { if ( triggerData && triggerData._selectBox === true ) { return; } self.showMenu(); }) .bind( 'close.selectBox', function ( event, triggerData ) { if ( triggerData && triggerData._selectBox === true ) { return; } self.hideMenus(); }) .insertAfter( select ); // Set label width var labelWidth = control.width() - arrow.outerWidth() - ( parseInt( label.css( 'paddingLeft' ) ) || 0 ) - ( parseInt( label.css( 'paddingRight' ) ) || 0 ); label.width( labelWidth ); this.disableSelection( control ); } // Store data for later use and show the control select .addClass( 'selectBox' ) .data( 'selectBox-control', control ) .data( 'selectBox-settings', settings ) .hide(); }; /** * @param {String} type 'inline'|'dropdown' * @returns {jQuery} */ SelectBox.prototype.getOptions = function ( type ) { var options; var select = $( this.selectElement ); var self = this; // Private function to handle recursion in the getOptions function. var _getOptions = function ( select, options ) { // Loop through the set in order of element children. select.children( 'OPTION, OPTGROUP' ).each( function () { // If the element is an option, add it to the list. if ( $( this ).is( 'OPTION' ) ) { // Check for a value in the option found. if ( $( this ).length > 0 ) { // Create an option form the found element. self.generateOptions( $( this ), options ); } else { // No option information found, so add an empty. options.append( '
  • \u00A0
  • ' ); } } else { // If the element is an option group, add the group and call this function on it. var optgroup = $( '
  • ' ); optgroup.text( $( this ).attr( 'label' ) ); options.append( optgroup ); options = _getOptions( $( this ), options ); } }); // Return the built string return options; }; switch ( type ) { case 'inline': options = $( '
      ' ); options = _getOptions( select, options ); options .find( 'A' ) .bind( 'mouseover.selectBox', function ( event ) { self.addHover( $( this ).parent() ); }) .bind( 'mouseout.selectBox',function ( event ) { self.removeHover( $( this ).parent() ); }) .bind( 'mousedown.selectBox',function ( event ) { if ( 1 !== event.which ) { return } // Prevent options from being "dragged" event.preventDefault(); if ( !select.selectBox( 'control' ).hasClass( 'selectBox-active' ) ) { select.selectBox( 'control' ).focus(); } }) .bind( 'mouseup.selectBox', function ( event ) { if ( 1 !== event.which ) { return; } self.hideMenus(); self.selectOption( $( this ).parent(), event ); }); this.disableSelection( options ); return options; case 'dropdown': options = $( '
        ' ); options = _getOptions( select, options ); options .data( 'selectBox-select', select ) .css( 'display', 'none' ) .appendTo( 'BODY' ) .find( 'A' ) .bind( 'mousedown.selectBox', function ( event ) { if ( event.which === 1 ) { event.preventDefault(); if ( event.screenX === options.data( 'selectBox-down-at-x' ) && event.screenY === options.data( 'selectBox-down-at-y' ) ) { options.removeData( 'selectBox-down-at-x' ).removeData( 'selectBox-down-at-y' ); if ( /android/i.test( navigator.userAgent.toLowerCase() ) && /chrome/i.test( navigator.userAgent.toLowerCase() ) ) { self.selectOption( $( this ).parent() ); } self.hideMenus(); } } }) .bind( 'mouseup.selectBox', function ( event ) { if ( 1 !== event.which ) { return; } if ( event.screenX === options.data( 'selectBox-down-at-x' ) && event.screenY === options.data( 'selectBox-down-at-y' ) ) { return; } else { options.removeData( 'selectBox-down-at-x' ).removeData( 'selectBox-down-at-y' ); } self.selectOption( $( this ).parent() ); self.hideMenus(); }) .bind( 'mouseover.selectBox', function ( event ) { self.addHover( $( this ).parent() ); }) .bind( 'mouseout.selectBox', function ( event ) { self.removeHover( $( this ).parent() ); }); // Inherit classes for dropdown menu var classes = select.attr( 'class' ) || ''; if ( '' !== classes ) { classes = classes.split( ' ' ); for ( var i = 0; i < classes.length; i++ ) { options.addClass( classes[i] + '-selectBox-dropdown-menu' ); } } this.disableSelection( options ); return options; } }; /** * Returns the current class of the selected option. * * @returns {String} */ SelectBox.prototype.getLabelClass = function () { var selected = $(this.selectElement).find('OPTION:selected'); return ('selectBox-label ' + (selected.attr('class') || '')).replace(/\s+$/, ''); }; /** * Returns the current label of the selected option. * * @returns {String} */ SelectBox.prototype.getLabelText = function () { var selected = $( this.selectElement ).find( 'OPTION:selected' ); return selected.text() || '\u00A0'; }; /** * Sets the label. * This method uses the getLabelClass() and getLabelText() methods. */ SelectBox.prototype.setLabel = function () { var select = $( this.selectElement ); var control = select.data( 'selectBox-control' ); if ( !control ) { return; } control .find( '.selectBox-label' ) .attr( 'class', this.getLabelClass() ) .text( this.getLabelText() ); }; /** * Destroys the SelectBox instance and shows the origin select element. * */ SelectBox.prototype.destroy = function () { var select = $( this.selectElement ); var control = select.data( 'selectBox-control' ); if ( !control ) { return; } var options = control.data( 'selectBox-options' ); options.remove(); control.remove(); select .removeClass( 'selectBox' ) .removeData( 'selectBox-control' ) .data( 'selectBox-control', null ) .removeData( 'selectBox-settings' ) .data( 'selectBox-settings', null ) .show(); }; /** * Refreshes the option elements. */ SelectBox.prototype.refresh = function () { var select = $( this.selectElement ), control = select.data( 'selectBox-control' ), type = control.hasClass( 'selectBox-dropdown' ) ? 'dropdown' : 'inline', options; // Remove old options control.data( 'selectBox-options' ).remove(); // Generate new options options = this.getOptions( type ); control.data( 'selectBox-options', options ); switch ( type ) { case 'inline': control.append( options ); break; case 'dropdown': // Update label this.setLabel(); $( "BODY" ).append( options ); break; } // Restore opened dropdown state (original menu was trashed) if ( 'dropdown' === type && control.hasClass( 'selectBox-menuShowing' ) ) { this.showMenu(); } }; /** * Shows the dropdown menu. */ SelectBox.prototype.showMenu = function () { var self = this, select = $( this.selectElement ), control = select.data( 'selectBox-control' ), settings = select.data( 'selectBox-settings' ), options = control.data( 'selectBox-options' ); if ( control.hasClass( 'selectBox-disabled' ) ) { return false; } this.hideMenus(); // Get top and bottom width of selectBox var borderBottomWidth = parseInt( control.css( 'borderBottomWidth' ) ) || 0; var borderTopWidth = parseInt( control.css( 'borderTopWidth' ) ) || 0; // Get proper variables for keeping options in viewport var pos = control.offset(), topPositionCorrelation = ( settings.topPositionCorrelation ) ? settings.topPositionCorrelation : 0, bottomPositionCorrelation = (settings.bottomPositionCorrelation) ? settings.bottomPositionCorrelation : 0, optionsHeight = options.outerHeight(), controlHeight = control.outerHeight(), maxHeight = parseInt(options.css('max-height')), scrollPos = $(window).scrollTop(), heightToTop = pos.top - scrollPos, heightToBottom = $(window).height() - ( heightToTop + controlHeight ), posTop = (heightToTop > heightToBottom) && (settings.keepInViewport == null ? true : settings.keepInViewport), top = posTop ? pos.top - optionsHeight + borderTopWidth + topPositionCorrelation : pos.top + controlHeight - borderBottomWidth - bottomPositionCorrelation; // If the height to top and height to bottom are less than the max-height if( heightToTop < maxHeight && heightToBottom < maxHeight ){ // Set max-height and top if( posTop ){ var maxHeightDiff = maxHeight - ( heightToTop - 5 ); options.css( { 'max-height': maxHeight - maxHeightDiff + 'px' } ); top = top + maxHeightDiff; } else { var maxHeightDiff = maxHeight - ( heightToBottom - 5 ); options.css( { 'max-height': maxHeight - maxHeightDiff + 'px' } ); } } // Save if position is top to options data options.data('posTop',posTop); // Menu position options .width( control.innerWidth() ) .css({ top: top, left: control.offset().left }) // Add Top and Bottom class based on position .addClass( 'selectBox-options selectBox-options-'+( posTop?'top':'bottom' ) ); if ( select.triggerHandler( 'beforeopen' ) ) { return false; } var dispatchOpenEvent = function () { select.triggerHandler( 'open', { _selectBox: true }); }; // Show menu switch ( settings.menuTransition ) { case 'fade': options.fadeIn( settings.menuSpeed, dispatchOpenEvent ); break; case 'slide': options.slideDown( settings.menuSpeed, dispatchOpenEvent ); break; default: options.show( settings.menuSpeed, dispatchOpenEvent ); break; } if ( !settings.menuSpeed ) { dispatchOpenEvent(); } // Center on selected option var li = options.find( '.selectBox-selected:first' ); this.keepOptionInView( li, true ); this.addHover( li ); control.addClass( 'selectBox-menuShowing selectBox-menuShowing-'+( posTop?'top':'bottom' ) ); $(document).bind('mousedown.selectBox', function ( event ) { if ( 1 === event.which ) { if ( $( event.target ).parents().andSelf().hasClass( 'selectBox-options' ) ) { return; } self.hideMenus(); } }); }; /** * Hides the menu of all instances. */ SelectBox.prototype.hideMenus = function () { if ( $( ".selectBox-dropdown-menu:visible" ).length === 0 ) { return; } $( document ).unbind( 'mousedown.selectBox' ); $( ".selectBox-dropdown-menu" ).each(function () { var options = $( this ), select = options.data( 'selectBox-select' ), control = select.data( 'selectBox-control' ), settings = select.data( 'selectBox-settings' ), posTop = options.data( 'posTop' ); if ( select.triggerHandler( 'beforeclose' ) ) { return false; } var dispatchCloseEvent = function () { select.triggerHandler( 'close', { _selectBox: true }); }; if ( settings ) { switch ( settings.menuTransition ) { case 'fade': options.fadeOut( settings.menuSpeed, dispatchCloseEvent ); break; case 'slide': options.slideUp( settings.menuSpeed, dispatchCloseEvent ); break; default: options.hide( settings.menuSpeed, dispatchCloseEvent ); break; } if ( !settings.menuSpeed ) { dispatchCloseEvent(); } control.removeClass( 'selectBox-menuShowing selectBox-menuShowing-' + ( posTop?'top':'bottom' ) ); } else { $( this ).hide(); $( this ).triggerHandler( 'close', { _selectBox: true }); $( this ).removeClass( 'selectBox-menuShowing selectBox-menuShowing-' + ( posTop?'top':'bottom' ) ); } options.css( 'max-height','' ) //Remove Top or Bottom class based on position options.removeClass( 'selectBox-options-'+( posTop?'top':'bottom' ) ); options.data( 'posTop' , false ); }); }; /** * Selects an option. * * @param {HTMLElement} li * @param {DOMEvent} event * @returns {Boolean} */ SelectBox.prototype.selectOption = function ( li, event ) { var select = $( this.selectElement ); li = $( li ); var control = select.data( 'selectBox-control' ), settings = select.data( 'selectBox-settings' ); if ( control.hasClass( 'selectBox-disabled' ) ) { return false; } if ( 0 === li.length || li.hasClass( 'selectBox-disabled' ) ) { return false; } if ( select.attr( 'multiple' ) ) { // If event.shiftKey is true, this will select all options between li and the last li selected if ( event.shiftKey && control.data( 'selectBox-last-selected' ) ) { li.toggleClass( 'selectBox-selected' ); var affectedOptions; if ( li.index() > control.data( 'selectBox-last-selected' ).index() ) { affectedOptions = li .siblings() .slice( control.data( 'selectBox-last-selected' ).index(), li.index() ); } else { affectedOptions = li .siblings() .slice( li.index(), control.data( 'selectBox-last-selected' ).index() ); } affectedOptions = affectedOptions.not( '.selectBox-optgroup, .selectBox-disabled' ); if ( li.hasClass( 'selectBox-selected' ) ) { affectedOptions.addClass( 'selectBox-selected' ); } else { affectedOptions.removeClass( 'selectBox-selected' ); } } else if ( ( this.isMac && event.metaKey ) || ( !this.isMac && event.ctrlKey ) ) { li.toggleClass( 'selectBox-selected' ); } else { li.siblings().removeClass( 'selectBox-selected' ); li.addClass( 'selectBox-selected' ); } } else { li.siblings().removeClass( 'selectBox-selected' ); li.addClass( 'selectBox-selected' ); } if ( control.hasClass( 'selectBox-dropdown' ) ) { control.find( '.selectBox-label' ).text( li.text() ); } // Update original control's value var i = 0, selection = []; if ( select.attr( 'multiple' ) ) { control.find( '.selectBox-selected A' ).each( function () { selection[ i++ ] = $( this ).attr( 'rel' ); }); } else { selection = li.find( 'A' ).attr( 'rel' ); } // Remember most recently selected item control.data( 'selectBox-last-selected', li ); // Change callback if ( select.val() !== selection ) { select.val( selection ); this.setLabel(); select.trigger( 'change' ); } return true; }; /** * Adds the hover class. * * @param {HTMLElement} li */ SelectBox.prototype.addHover = function( li ) { li = $( li ); var select = $( this.selectElement ), control = select.data( 'selectBox-control' ), options = control.data( 'selectBox-options' ); options.find( '.selectBox-hover' ).removeClass( 'selectBox-hover' ); li.addClass( 'selectBox-hover' ); }; /** * Returns the original HTML select element. * * @returns {HTMLElement} */ SelectBox.prototype.getSelectElement = function () { return this.selectElement; }; /** * Remove the hover class. * * @param {HTMLElement} li */ SelectBox.prototype.removeHover = function( li ) { li = $( li ); var select = $(this.selectElement), control = select.data('selectBox-control'), options = control.data('selectBox-options'); options.find( '.selectBox-hover' ).removeClass( 'selectBox-hover' ); }; /** * Checks if the widget is in the view. * * @param {jQuery} li * @param {Boolean} center */ SelectBox.prototype.keepOptionInView = function ( li, center ) { if ( !li || li.length === 0 ) { return; } var select = $( this.selectElement ), control = select.data('selectBox-control'), options = control.data('selectBox-options'), scrollBox = control.hasClass('selectBox-dropdown') ? options : options.parent(), top = parseInt(li.offset().top -scrollBox.position().top), bottom = parseInt(top + li.outerHeight()); if ( center ) { scrollBox.scrollTop( li.offset().top - scrollBox.offset().top + scrollBox.scrollTop() - ( scrollBox.height() / 2 ) ); } else { if ( top < 0 ) { scrollBox.scrollTop( li.offset().top - scrollBox.offset().top + scrollBox.scrollTop() ); } if ( bottom > scrollBox.height() ) { scrollBox.scrollTop( ( li.offset().top + li.outerHeight()) - scrollBox.offset().top + scrollBox.scrollTop() - scrollBox.height() ); } } }; /** * Enables the selectBox. */ SelectBox.prototype.enable = function () { var select = $( this.selectElement ); select.prop( 'disabled', false ); var control = select.data( 'selectBox-control' ); if ( !control ) { return; } control.removeClass( 'selectBox-disabled' ); }; /** * Disables the selectBox. */ SelectBox.prototype.disable = function () { var select = $( this.selectElement ); select.prop( 'disabled', true ); var control = select.data( 'selectBox-control' ); if ( !control ) { return; } control.addClass( 'selectBox-disabled' ); }; /** * Sets the current value. * * @param {String} value */ SelectBox.prototype.setValue = function ( value ) { var select = $( this.selectElement ); select.val( value ); value = select.val(); if ( null === value ) { value = select.children().first().val(); select.val( value ); } var control = select.data( 'selectBox-control' ); if ( !control ) { return; } var settings = select.data( 'selectBox-settings' ), options = control.data( 'selectBox-options' ); // Update label this.setLabel(); // Update control values options.find( '.selectBox-selected' ).removeClass( 'selectBox-selected' ); options.find( 'A' ).each( function () { if ( typeof( value ) === 'object' ) { for ( var i = 0; i < value.length; i++ ) { if ( $( this ).attr( 'rel' ) == value[ i ] ) { $( this ).parent().addClass( 'selectBox-selected' ); } } } else { if ( $( this ).attr( 'rel' ) == value ) { $( this ).parent().addClass( 'selectBox-selected' ); } } }); if ( settings.change ) { settings.change.call( select ); } }; /** * Sets the option elements. * * @param {String|Object} options */ SelectBox.prototype.setOptions = function ( options ) { var select = $( this.selectElement ), control = select.data( 'selectBox-control' ); switch ( typeof( options ) ) { case 'string': select.html( options ); break; case 'object': select.html( '' ); for ( var i in options ) { if ( options[ i ] === null ) { continue; } if ( typeof( options[ i ] ) === 'object' ) { var optgroup = $( '' ); for ( var j in options[ i ] ) { optgroup.append( '' ); } select.append( optgroup ); } else { var option = $( '' ); select.append( option ); } } break; } if ( control ) { // Refresh the control this.refresh(); } }; /** * Disables the selection. * * @param {*} selector */ SelectBox.prototype.disableSelection = function ( selector ) { $(selector).css( 'MozUserSelect', 'none' ).bind( 'selectstart', function ( event ) { event.preventDefault(); }); }; /** * Generates the options. * * @param {jQuery} self * @param {jQuery} options */ SelectBox.prototype.generateOptions = function ( self, options ) { var li = $( '
      • ' ), a = $( '' ); li.addClass( self.attr( 'class' ) ); li.data( self.data() ); a.attr( 'rel', self.val() ).text( self.text() ); li.append( a ); if ( self.attr( 'disabled' ) ) { li.addClass( 'selectBox-disabled' ); } if ( self.attr( 'selected' ) ) { li.addClass( 'selectBox-selected' ); } options.append( li ); }; /** * Extends the jQuery.fn object. */ $.extend( $.fn, { selectBox: function ( method, options ) { var selectBox; switch ( method ) { case 'control': return $( this ).data( 'selectBox-control' ); case 'settings': if ( !options ) { return $( this ).data( 'selectBox-settings' ); } $( this ).each(function () { $( this ).data( 'selectBox-settings', $.extend( true, $( this ).data( 'selectBox-settings' ), options ) ); }); break; case 'options': // Getter if ( undefined === options ) { return $( this ).data( 'selectBox-control' ).data( 'selectBox-options' ); } // Setter $( this ).each( function () { if ( selectBox = $( this ).data( 'selectBox' ) ) { selectBox.setOptions( options ); } }); break; case 'value': // Empty string is a valid value if ( undefined === options ) { return $( this ).val(); } $( this ).each( function () { if ( selectBox = $( this ).data( 'selectBox' ) ) { selectBox.setValue( options ); } }); break; case 'refresh': $( this ).each( function () { if ( selectBox = $( this ).data( 'selectBox' ) ) { selectBox.refresh(); } }); break; case 'enable': $( this ).each( function () { if ( selectBox = $( this ).data( 'selectBox' ) ) { selectBox.enable( this ); } }); break; case 'disable': $( this ).each( function () { if ( selectBox = $( this ).data( 'selectBox' ) ) { selectBox.disable(); } }); break; case 'destroy': $( this ).each( function () { if ( selectBox = $( this ).data( 'selectBox' ) ) { selectBox.destroy(); $( this ).data( 'selectBox', null ); } }); break; case 'instance': return $( this ).data( 'selectBox' ); default: $( this ).each( function ( idx, select ) { if ( !$( select ).data( 'selectBox' ) ) { $( select ).data( 'selectBox', new SelectBox( select, method ) ); } }); break; } return $( this ); } }); })( jQuery );