(function($) { /***************************************************************** * Select *****************************************************************/ function Select(el, options) { this.$el = $(el); this.id = Math.random(); this.options = options; this.multiple = this.$el.prop('multiple'); this.activeOpt = null; this.widthSet = false; this.generate(); } Select.prototype = { generate: function() { if (!this.$select) { var _self = this; this.$select = $('
'); this.$optionsHolder = $('
'); this.$options = $('
'); // ie 7 fix to get proper zIndex on select dropdowns if(!$.support.placeholder) { this.$select.css('zIndex', 100 - this.$el.index()); } var click = function(e) { e.stopPropagation(); $('select').each(function() { var wSelect = $(this).data('wSelect'); if (wSelect && wSelect.id !== _self.id) { if (!wSelect.multiple) { wSelect.$optionsHolder.hide(); } wSelect.onBlur(); } }); if (!_self.multiple) { _self.onClick(e); } _self.$el.focus(); }; if (this.multiple) { this.$select.addClass('wSelect-multiple'); this.$optionsHolder.click(click); } else { this.$selected = $('
'); this.$select.append(this.$selected); this.$select.click(click); this.$optionsHolder.click(function(e) { e.stopPropagation(); _self.$el.focus(); }); } this.$select.hover( function(){ _self.onFocus('hover'); }, function(){ _self.onBlur('hover'); } ); this.$el.addClass('wSelect-el') .change(function() { _self.change(); }) .focus(function() { _self.onFocus(); }) .keydown(function(e) { _self.keydown(e); }) .keyup(function(e) { _self.keyup(e); }); $(document).click(function() { if (!_self.multiple) { _self.$optionsHolder.hide(); } _self.onBlur(); }); this.widthSet = this.$select.width() > 0; this.setTheme(this.options.theme); this.setSize(this.options.size); this.reset(); this.$optionsHolder.append(this.$options); this.$select.append(this.$optionsHolder); this.$el.after(this.$select);//.hide(); } return this.$select; }, reset: function() { var _self = this; this.$options.children().remove(); this.$el.children().each(function() { var option = new Option(this, _self); $.data(this, 'wSelect-option', option); _self.$options.append(option.generate()); }); this.$options.children().removeClass('wSelect-option-last').last().addClass('wSelect-option-last'); this.setSize(this.options.size); }, change: function() { this.$options.children().removeClass('wSelect-option-selected wSelect-option-active'); this.$el.children(':selected').each(function() { $(this).data('wSelect-option').select(); }); }, keydown: function(e) { // tab if (e.keyCode === 9) { this.$optionsHolder.hide(); this.onBlur(); } }, keyup: function(e) { // enter if (e.keyCode === 13) { this.$optionsHolder.hide(); } // left, up, right, down else if (e.keyCode >= 37 && e.keyCode <= 40) { this.change(); var $option = this.$options.find('.wSelect-option-selected:last'), scrollTop = this.$options.scrollTop(), top = $option.position().top + scrollTop, optionsHeight = this.$options.height(), optionHeight = $option.outerHeight(true); if (top - scrollTop < 0) { this.$options.scrollTop(top); } else if (top + optionHeight - scrollTop > optionsHeight) { this.$options.scrollTop(top - optionsHeight + optionHeight); } } }, onClick: function(e) { // find best fit for dropdowns (top or bottom) if (!this.$optionsHolder.is(':visible')) { var top = this.$select.offset().top - $(window).scrollTop(), optionsHeight = this.$optionsHolder.outerHeight(), topDiff = top - optionsHeight, botDiff = $(window).height() - (top + this.$select.outerHeight() + optionsHeight + 5), // 5 is just for some bottom screen padding newTop = (botDiff > 0 || botDiff > topDiff) ? this.$select.height() : -optionsHeight; this.$optionsHolder.css('top', newTop); } this.$optionsHolder.toggle(); }, onFocus: function(className) { className = className || 'active'; if (this.options.highlight) { this.$select.addClass('wSelect-' + className); } }, onBlur: function(className) { className = className || 'active'; if (this.options.highlight) { this.$select.removeClass('wSelect-' + className); } }, setTheme: function(theme) { this.$select.attr('class', this.$select.attr('class').replace(/wSelect-theme-.+\s|wSelect-theme-.+$/, '')); this.$select.addClass('wSelect-theme-' + theme); }, setSize: function(size) { var $option = this.$options.children(':first').clone().css({position:'absolute', left:-10000}), numOptions = this.$el.children().length, height; $('body').append($option); height = $option.outerHeight(true); $option.remove(); if (!this.multiple && size > numOptions) { size = numOptions; } this.$options.height(height * size - 1); } }; /***************************************************************** * Option *****************************************************************/ function Option(el, wSelect) { this.$el = $(el); this.wSelect = wSelect; } Option.prototype = { generate: function() { var _self = this; if (!this.$option) { var icon = this.$el.attr('data-icon'); this.$option = $('
'); this.$value = $('
'); this.$option.append(this.$value); if (typeof icon === 'string') { this.$value.addClass('wSelect-option-icon'); this.$value.css('backgroundImage', 'url(' + icon + ')'); } } if (this.$el.prop('selected')) { this.select(); } if (this.$el.prop('disabled')) { this.$option.addClass('wSelect-option-disabled'); } else { this.$option.removeClass('wSelect-option-disabled'); this.$option.unbind('click').click(function(e){ _self.onClick(e); }); } this.$value.html(this.$el.html()); // in case html has changed we always set it here this.setWidth(); return this.$option; }, select: function() { if (!this.wSelect.activeOpt) { this.wSelect.activeOpt = this; } if (!this.wSelect.multiple) { var icon = this.$el.attr('data-icon'); if (typeof icon === 'string') { this.wSelect.$selected.addClass('wSelect-option-icon'); this.wSelect.$selected.css('backgroundImage', 'url(' + icon + ')'); } else { this.wSelect.$selected.removeClass('wSelect-option-icon'); this.wSelect.$selected.css('backgroundImage', ''); } //if(!this.wSelect.focus) { this.wSelect.$optionsHolder.hide(); } this.wSelect.$selected.html(this.$el.html()); } this.$option.addClass('wSelect-option-selected'); }, onClick: function(e) { var selVal = null; if (this.wSelect.multiple && (e.ctrlKey || e.shiftKey) ) { if (e.ctrlKey || !this.wSelect.activeOpt) { selVal = this.wSelect.$el.val() || []; var optVal = this.$el.val(), arrayPos = $.inArray(optVal, selVal); if (arrayPos === -1) { selVal.push(this.$el.val()); this.wSelect.activeOpt = this; // only set active when "selecting" } else { selVal.splice(arrayPos, 1); } } // don't set active here as the shift+click only highlights from active option else if (e.shiftKey) { var indexActive = this.wSelect.activeOpt.$el.index(), indexCurrent = this.$el.index(), indexStart = 0, indexEnd = 0, $option = null; if (indexCurrent > indexActive) { indexStart = indexActive; indexEnd = indexCurrent; } else { indexStart = indexCurrent; indexEnd = indexActive; } selVal = []; for (var i=indexStart; i<=indexEnd; i++) { $option = this.wSelect.$el.children(':eq(' + i + ')'); if ($option.is(':not(:disabled)')) { selVal.push($option.val()); } } } } else { selVal = this.$el.val(); this.wSelect.$optionsHolder.hide(); this.wSelect.activeOpt = this; } this.wSelect.$el.val(selVal).change(); }, // help us set the proper widths based on given values (this way so we can add options on the fly one at a time) setWidth: function() { if (this.wSelect.multiple || this.wSelect.widthSet) { return true; } this.$option.hide().appendTo('body'); var optionWidth = this.$option.width(); if (optionWidth > this.wSelect.$select.width()) { this.wSelect.$select.width(optionWidth); } this.$option.detach().show(); } }; /***************************************************************** * fn.wSelect *****************************************************************/ $.support.placeholder = 'placeholder' in document.createElement('input'); $.fn.wSelect = function(options, value) { if (typeof options === 'string') { var values = []; var elements = this.each(function() { var wSelect = $(this).data('wSelect'); if (wSelect) { var func = (value ? 'set' : 'get') + options.charAt(0).toUpperCase() + options.substring(1).toLowerCase(); if (wSelect[options]) { wSelect[options].apply(wSelect, [value]); } else if (value) { if (wSelect[func]) { wSelect[func].apply(wSelect, [value]); } if (wSelect.options[options]) { wSelect.options[options] = value; } } else { if(wSelect[func]) { values.push(wSelect[func].apply(wSelect, [value])); } else if (wSelect.options[options]) { values.push(wSelect.options[options]); } else { values.push(null); } } } }); if (values.length === 1) { return values[0]; } else if (values.length > 0) { return values; } else { return elements; } } options = $.extend({}, $.fn.wSelect.defaults, options); function get(el) { var wSelect = $.data(el, 'wSelect'); if (!wSelect) { var _options = jQuery.extend(true, {}, options); _options.size = $(el).prop('size') || _options.size; wSelect = new Select(el, _options); $.data(el, 'wSelect', wSelect); } return wSelect; } return this.each(function() { get(this); }); }; $.fn.wSelect.defaults = { theme: 'classic', // theme size: '4', // default number of options to display (overwrite with `size` attr on `select` element) highlight: true // highlight fields when selected }; })(jQuery);