// Copyright (c) 2012 Ivan Kubrakov // Selectik a jQuery custom select plugin http://brankub.github.com/selectik/ // Added jquery.actual plugin support for hidden elements - Roman Zhbadynskyi (function($) { // global variables var openList = false; var selectControl = false; var trigger = false; var mouseTrigger = false; var isMobile = (/android|iphone|ipad|ipod|android/i.test(navigator.userAgent.toLowerCase())); var isOpera = (/opera/i.test(navigator.userAgent.toLowerCase())); var isIE = (/msie/i.test(navigator.userAgent.toLowerCase())); var isOperaMini = Object.prototype.toString.call(window.operamini) === "[object OperaMini]"; Selectik = function(options){ // merge options this.config = $.extend(true, { containerClass: 'custom-select', width: 0, maxItems: 0, customScroll: 1, speedAnimation:200, smartPosition: true }, options || {}); }; Selectik.prototype = { _init: function(element){ this.$cselect = $(element); this.cselect = element; this.scrollL = false; this.change = false; //Check select width //Select width is inconsistent in different browser, //so we wrap select by inline element and get it's width if( this.config.width == 0 ) { this.$cselect.wrap(''); this.config.width = this.$cselect.parent().width(); this.$cselect.parent().replaceWith( this.$cselect ); } this._generateContainer(); this._handlers(); }, // private method: wrap selects in divs and fire html generator _generateContainer: function(){ this.$cselect.wrap('
'); this.$container = this.$cselect.parent(); this._getList({ refreshHtml: false }); }, // private method: generate list html _getList: function(e){ this.count = $('option:not(.hidden)', this.$cselect).length; if (e.refreshSelect){ $('.select-list', this.$container).remove(); } // loop html var html = this._generateHtml(); // html for control var scrollHtml = (this.config.maxItems > 0 && this.config.customScroll == 1) ? '
' : ''; var scrollClass = (this.config.customScroll == 1) ? 'custom-scroll' : 'default-scroll'; // selected this.$selected = $('option:selected', this.$cselect); // check if first time or refresh if (e.refreshSelect){ html = '
'+scrollHtml+'
'; $(html).prependTo(this.$container); }else{ html = ''+this.$selected[0].text+'
'+scrollHtml+'
'; $(html).prependTo(this.$container); } this.$list = $('ul', this.$container); this.$text = $('.custom-text', this.$container); this.$listContainer = $('.select-list', this.$container); this._clickHandler(); // give class to the selected element $('li:eq('+(this.$selected.index())+')', this.$list).addClass('selected'); // give width to elements this.$container.removeClass('done'); if (typeof(this.$cselect.actual) == 'function') { var width = (this.config.width > 0) ? this.config.width : this.$cselect.actual('outerWidth'); } else { var width = (this.config.width > 0) ? this.config.width : this.$cselect.outerWidth(); } this.setWidthCS(width); // standard top distance this.standardTop = parseInt(this.$listContainer.css('top')); // fire function for max length this._getLength({refreshSelect: e.refreshSelect }); }, // html for custom select _generateHtml: function(){ this.$collection = this.$cselect.children(); var html = ''; for (var i = 0; i < this.$collection.length; i++){ var $this = $(this.$collection[i]); html += '
  • '+($this.data('selectik') ? $this.data('selectik') : $this[0].text)+'
  • '; }; return html; }, _getLength: function(e){ if (!e.refreshSelect){ this.heightItem = (typeof(this.$cselect.actual) == 'function') ? $('li:nth-last-child(1)', this.$list).actual('outerHeight') : $('li:nth-last-child(1)', this.$list).outerHeight(); } // check if count of options more then max if (this.count < this.config.maxItems || this.config.maxItems == 0) { this.$listContainer.hide(); this.$container.addClass('done'); this.scrollL = false; return; } this.scrollL = true; this.heightList = this.heightItem*this.count; this.heightContainer = this.heightItem*this.config.maxItems; // put height for list this.$list.css('height', this.heightContainer); this.$listContainer.hide(); this.$container.addClass('done'); if (this.config.customScroll == 1) { this._getScroll(); } }, // private method: custom scroll _getScroll: function(){ var allHeight = this.heightItem*this.count; this.heightShift = -allHeight+this.heightContainer; this.$bgScroll = $('.select-scroll', this.$listContainer); this.$bgScroll.css('height', this.heightContainer); this.$scroll = $('.scroll-drag', this.$listContainer); this.$listContainer.addClass('maxlength'); // calculate relate of heights this.relating = allHeight / this.heightContainer; // height of scroll this.heightScroll = this.heightContainer*(this.heightContainer / allHeight); this.$scroll.css('height', this.heightScroll); // if selected if ($('.selected', this.$list).length > 0){ this._shift($('.selected', this.$list).index()); } if (this.config.customScroll){ this._scrollHandlers(); } }, _scrollHandlers: function(){ var shiftL; var selectik = this; // bind mousewheel this.$list.bind('mousewheel', function(event, deltaY) { shiftL = parseInt(selectik.$list.css('top'))+(deltaY*selectik.heightItem); selectik._shiftHelper(shiftL); return false; }); // bind click on scroll background this.$bgScroll.click(function(e){ var direction = (((e.pageY - $(this).offset().top)/selectik.heightContainer) > 0.5) ? -1 : 1; shiftL = parseInt(selectik.$list.css('top')) + (selectik.heightItem * direction); selectik._shiftHelper(shiftL); return false; }); // bind scroll on mousedown selecting this.$text.on('mousedown', function(e){ selectik._draggable(e, true); }); // draggable handler and calculate this.$scroll.on('mousedown', function(e){ mouseTrigger = false; selectik._draggable(e, true); }); $(document).on('mouseup', function(e){ selectik._draggable(e, false); }); }, // private method: draggable for scroll _draggable: function(e, on){ var selectik = this; if (on){ openList = false; if (e.preventDefault()) { e.preventDefault(); } var startPosition = parseInt(selectik.$scroll.css('top')); var helper = e.clientY; var textHeight = selectik.$text.outerHeight(); $(document).bind('mousemove', function(e){ if (mouseTrigger){ var listTopPosition = selectik.$list.parent().offset().top; var listHeight = selectik.$list.outerHeight(); var direction = (selectik.topPosition > 0) ? -1 : 1; var difference = (selectik.topPosition > 0) ? textHeight : 0; var cursorPosition = (e.clientY - (listTopPosition - difference - $(window).scrollTop())); if (cursorPosition < 0 || cursorPosition > (listHeight + textHeight)){ var deltaY = (cursorPosition < 0) ? 1 : -1; var shiftL = parseInt(selectik.$list.css('top'))+(deltaY*selectik.heightItem); selectik._shiftHelper(shiftL); } }else{ var newPosition = (helper - e.clientY) - startPosition; selectik._shiftHelper(newPosition*selectik.relating); } }); }else{ $(document).unbind('mousemove'); openList = true; } }, // private method: shift _shiftHelper: function (e){ e = (e > 0) ? 0 : e; e = (e < this.heightShift) ? this.heightShift: e; this.$list.css('top', e); this.$scroll.css('top', -e/this.relating); }, // private method: shift conrtol _shift: function(indexEl){ if (indexEl < 0 || indexEl == this.count) { return; } this.topShift = (indexEl > this.count-this.config.maxItems) ? this.heightList-this.heightContainer : this.heightItem*indexEl; $('.selected', this.$list).removeClass('selected'); var $selectedLi = $('li:nth-child('+(indexEl+1)+')', this.$list); $selectedLi.addClass('selected'); if (openList && selectControl){ this.$text.text($selectedLi.data('value')); } if (!this.scrollL) { return; } this._shiftHelper(-this.topShift); }, // private method: click on li _clickHandler: function(){ var selectik = this; this.$listContainer.off('mousedown', 'li').on('mousedown', 'li', function(e){ if (selectik.change) { selectik.change = false; return true; } if ($(this).hasClass('disabled')) { e.preventDefault(); return; }; selectik.change = true; selectik._changeSelected($(this)); }); }, // private method: handlers _handlers: function(){ // reset button var selectik = this; var $reset = $('input[type="reset"]', this.$cselect.parents('form')); if ($reset.length > 0){ $reset.bind('click',function(){ var index = (selectik.$selected.length > 0) ? selectik.$selected.index(): 0; selectik._changeSelected($('option:eq('+index+')', selectik.$cselect)); }); } // change on original select this.$cselect.bind('change', function(){ if (selectik.change) { selectik.change = false; return true; } selectik._changeSelected($('option:selected', $(this))); }); // click on select this.$text.bind('click', function(e){ if( selectik.$container.hasClass('disable') || mouseTrigger) { return false; } selectik.$cselect.focus(); selectik._fadeList(false, true); }); // mouse down/up var mouseDown = false; this.$text.bind('mousedown', function(e){ e.preventDefault() mouseDown = true; setTimeout(function(){ if (mouseDown){ mouseTrigger = true; selectik._fadeList(false, true); } }, 600); }); this.$text.bind('mouseup', function(){ mouseDown = false; }); this.$listContainer.on('mousedown', 'li',function(e){ selectik.hideCS(true); }); this.$listContainer.on('mouseup', 'li',function(e){ if (!mouseTrigger) { return true; } if ($(this).hasClass('disabled')) { return; }; selectik.change = true; selectik._changeSelected($('option:eq('+$(e.currentTarget).index()+')', selectik.$cselect)); selectik.hideCS(true); mouseTrigger = false; selectik.$cselect.focus(); }); // active class this.$cselect.bind('focus', function(){ selectik.$container.addClass('active'); }); this.$cselect.bind('blur', function(){ if (mouseTrigger) return; selectik.hideCS(true); selectik.$container.removeClass('active'); }); this.$cselect.bind('keyup', function(e) { selectik._keysHandlers(e); }); if (isOpera){ selectik.$cselect.bind('keydown', function() { trigger = true; }); }; }, // private method: handlers on keys _keysHandlers: function(e){ if (e.keyCode == 13 && this.$listContainer.is(':visible')) { this._fadeList(true, false); } if (!isIE){ if (e.keyCode == 27 && this.$listContainer.is(':visible')) { this._fadeList(true, true); } } // don't change until blur //this.$cselect.change(); if (this.scrollL) { this._shift($('option:selected', this.$cselect).index()); } }, // private method: change selected _changeSelected: function(e){ var dataValue = (e.parents('select').length > 0) ? e.attr('value') : e.data('value'); var textValue = e.text(); this._changeSelectedHtml(dataValue, textValue, e.index()+1); }, // private method: change selected _changeSelectedHtml: function(dataValue, textValue, index){ if (index > this.count || index == 0) { return false;} this.change = true; var $selected = $('.selected', this.$list); $('option:eq('+$selected.index()+')', this.$cselect).prop('selected', false); // $('option:eq('+(index-1)+')', this.$cselect).prop('selected', true); this.$cselect.prop('value', dataValue).change(); $selected.removeClass('selected'); $('li:nth-child('+ index +')', this.$list).addClass('selected'); this.$text.text(textValue); }, // private method: show/hdie list _fadeList: function(out, text){ var $openList = $('.'+this.config.containerClass+'.open_list'); if ($openList.length == 1){ $openList.children('select').data('selectik').hideCS(); return; } if (!text){ $openList.children('.select-list').stop(true, true).fadeOut(this.config.speedAnimation).parent().toggleClass('open_list'); if (out){ return; } } openList = false; this.positionCS(); this.$listContainer.stop(true, true).fadeToggle(this.config.speedAnimation); this.$listContainer.parent().toggleClass('open_list'); var selectik = this; setTimeout(function(){ openList = true; }, selectik.config.speedAnimation); }, // public method: hide list hideCS: function(next){ this.$listContainer.fadeOut(this.config.speedAnimation); this.$container.removeClass('open_list'); if (!next) this.$cselect.focus(); openList = true; }, // public method: show list showCS: function(){ openList = false; this.$listContainer.fadeIn(this.config.speedAnimation); this.$container.addClass('open_list'); }, // public method: postion of list positionCS: function(){ if (!this.config.smartPosition) return; elParent = this.$listContainer.parent(); var heightPosition = (this.scrollL) ? this.config.maxItems*this.heightItem : this.count*this.heightItem; var quaItems = (this.scrollL) ? this.config.maxItems : this.count; var $window = $(window); var widnowScrollTop = $window.scrollTop(); var topPosition = ($window.height() - (elParent.offset().top - widnowScrollTop) - elParent.outerHeight() < heightPosition) ? -quaItems*this.heightItem-(elParent.outerHeight()/4) : this.standardTop; this.topPosition = ((elParent.offset().top - widnowScrollTop) < heightPosition && ($('html').height() - elParent.offset().top) > heightPosition) ? this.standardTop : topPosition; this.$listContainer.css('top', this.topPosition); }, // public method: refresh list refreshCS: function() { this._getList({ refreshSelect: true }); }, // public method: change active element changeCS: function(val) { var index = (val.index > 0) ? val.index : $('option[value="'+val.value+'"]', this.$cselect).index() + 1; var $option = $('option:nth-child('+(index)+')', this.$cselect); var dataValue = $option.attr('value'); var textValue = $option.text(); this._changeSelectedHtml(dataValue, textValue, index); }, // public method: disable list disableCS: function(){ this.$cselect.attr('disabled', true); this.$container.addClass('disable'); }, // public method: enable list enableCS: function(){ this.$cselect.attr('disabled', false); this.$container.removeClass('disable'); }, // public method: required requiredCS: function(){ this.$text.toggleClass('required'); }, // public method: width of select setWidthCS: function(width){ //Paddings may has element or/and it's parent $.each([this.$list,this.$text],function() { var $parent = $(this).parent(), parentPaddings = $parent.outerWidth() - $parent.width(), elementPaddings = $(this).outerWidth() - $(this).width(), paddings = parentPaddings + elementPaddings; // Do this to backward compability //$(this).css('width', width - paddings); $(this).css('width', width); }); } }; $.fn.selectik = function(options, methods) { if (isMobile || isOperaMini) return; return this.each(function() { if ($('optgroup', this).length > 0 || $(this).attr('multiple') == 'multiple') { return; } if (undefined == $(this).data('Selectik')) { // create a new instance of the plugin var selectik = new Selectik(options); // apply new methods for (i in methods){ selectik[i] = methods[i]; } // fire selectik selectik._init(this); $(this).data('selectik', selectik).trigger('init_done'); } }); }; // global handlers $(window).resize(function(){ if (openList){ var $list = $('.open_list'); if (!$list.length > 0) { return; } $list.children('select').data('selectik').positionCS($('.select-list:visible')); } }); $(document).bind('click', function(e){ if ($(e.target).hasClass('disabled')) { return; } if (trigger) { trigger = false; return; } if (openList){ openList = false; var $list = $('.open_list'); if ($list.length > 0){ var $select = $list.children('select'); $select.data('selectik').hideCS(); } } }); })(jQuery); $.fn.sort_select_box = function(){ var opt_value = $(this).val(); var my_options = $('option', $(this)); my_options.sort(function(a,b) { if (a.text > b.text) return 1; else if (a.text < b.text) return -1; else return 0 }) $(this).empty().append( my_options ); $(this).val(opt_value); if($(this).data('selectik')) $(this).data('selectik').refreshCS(); return this; }