|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797 |
- /*jshint asi:true, expr:true */
- /**
- * Plugin Name: Combo Select
- * Author : Vinay@Pebbleroad
- * Date: 23/11/2014
- * Description:
- * Converts a select box into a searchable and keyboard friendly interface. Fallbacks to native select on mobile and tablets
- */
-
- // Expose plugin as an AMD module if AMD loader is present:
- (function (factory) {
- 'use strict';
- if (typeof define === 'function' && define.amd) {
- // AMD. Register as an anonymous module.
- define(['jquery'], factory);
- } else if (typeof exports === 'object' && typeof require === 'function') {
- // Browserify
- factory(require('jquery'));
- } else {
- // Browser globals
- factory(jQuery);
- }
- }(function ( $, undefined ) {
-
- var pluginName = "comboSelect",
- dataKey = 'comboselect';
- var defaults = {
- comboClass : 'combo-select',
- comboArrowClass : 'combo-arrow',
- comboDropDownClass : 'combo-dropdown',
- inputClass : 'combo-input text-input',
- disabledClass : 'option-disabled',
- hoverClass : 'option-hover',
- selectedClass : 'option-selected',
- markerClass : 'combo-marker',
- themeClass : '',
- maxHeight : 200,
- extendStyle : true,
- focusInput : true
- };
-
- /**
- * Utility functions
- */
-
- var keys = {
- ESC: 27,
- TAB: 9,
- RETURN: 13,
- LEFT: 37,
- UP: 38,
- RIGHT: 39,
- DOWN: 40,
- ENTER: 13,
- SHIFT: 16
- },
- isMobile = (/android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(navigator.userAgent.toLowerCase()));
-
- /**
- * Constructor
- * @param {[Node]} element [Select element]
- * @param {[Object]} options [Option object]
- */
- function Plugin ( element, options ) {
-
- /* Name of the plugin */
-
- this._name = pluginName;
-
- /* Reverse lookup */
-
- this.el = element
-
- /* Element */
-
- this.$el = $(element)
-
- /* If multiple select: stop */
-
- if(this.$el.prop('multiple')) return;
-
- /* Settings */
-
- this.settings = $.extend( {}, defaults, options, this.$el.data() );
-
- /* Defaults */
-
- this._defaults = defaults;
-
- /* Options */
-
- this.$options = this.$el.find('option, optgroup')
-
- /* Initialize */
-
- this.init();
-
- /* Instances */
-
- $.fn[ pluginName ].instances.push(this);
-
- }
-
- $.extend(Plugin.prototype, {
- init: function () {
-
- /* Construct the comboselect */
-
- this._construct();
-
-
- /* Add event bindings */
-
- this._events();
-
-
- },
- _construct: function(){
-
- var self = this
-
- /**
- * Add negative TabIndex to `select`
- * Preserves previous tabindex
- */
-
- this.$el.data('plugin_'+ dataKey + '_tabindex', this.$el.prop('tabindex'))
-
- /* Add a tab index for desktop browsers */
-
- !isMobile && this.$el.prop("tabIndex", -1)
-
- /**
- * Wrap the Select
- */
-
- this.$container = this.$el.wrapAll('<div class="' + this.settings.comboClass + ' '+ this.settings.themeClass + '" />').parent();
-
- /**
- * Check if select has a width attribute
- */
- if(this.settings.extendStyle && this.$el.attr('style')){
-
- this.$container.attr('style', this.$el.attr("style"))
-
- }
-
-
- /**
- * Append dropdown arrow
- */
-
- this.$arrow = $('<div class="'+ this.settings.comboArrowClass+ '" />').appendTo(this.$container)
-
-
- /**
- * Append dropdown
- */
-
- this.$dropdown = $('<ul class="'+this.settings.comboDropDownClass+'" />').appendTo(this.$container)
-
-
- /**
- * Create dropdown options
- */
-
- var o = '', k = 0, p = '';
-
- this.selectedIndex = this.$el.prop('selectedIndex')
-
- this.$options.each(function(i, e){
-
- if(e.nodeName.toLowerCase() == 'optgroup'){
-
- return o+='<li class="option-group">'+this.label+'</li>'
- }
-
- if(!e.value) p = e.innerHTML
-
- o+='<li class="'+(this.disabled? self.settings.disabledClass : "option-item") + ' ' +(k == self.selectedIndex? self.settings.selectedClass : '')+ '" data-index="'+(k)+'" data-value="'+this.value+'">'+ (this.innerHTML) + '</li>'
-
- k++;
- })
-
- this.$dropdown.html(o)
-
- /**
- * Items
- */
-
- this.$items = this.$dropdown.children();
-
-
- /**
- * Append Input
- */
-
- this.$input = $('<input type="text"' + (isMobile? 'tabindex="-1"': '') + ' placeholder="'+p+'" class="'+ this.settings.inputClass + '">').appendTo(this.$container)
-
- /* Update input text */
-
- this._updateInput()
-
- },
-
- _events: function(){
-
- /* Input: focus */
-
- this.$container.on('focus.input', 'input', $.proxy(this._focus, this))
-
- /**
- * Input: mouseup
- * For input select() event to function correctly
- */
- this.$container.on('mouseup.input', 'input', function(e){
- e.preventDefault()
- })
-
- /* Input: blur */
-
- this.$container.on('blur.input', 'input', $.proxy(this._blur, this))
-
- /* Select: change */
-
- this.$el.on('change.select', $.proxy(this._change, this))
-
- /* Select: focus */
-
- this.$el.on('focus.select', $.proxy(this._focus, this))
-
- /* Select: blur */
-
- this.$el.on('blur.select', $.proxy(this._blurSelect, this))
-
- /* Dropdown Arrow: click */
-
- this.$container.on('click.arrow', '.'+this.settings.comboArrowClass , $.proxy(this._toggle, this))
-
- /* Dropdown: close */
-
- this.$container.on('comboselect:close', $.proxy(this._close, this))
-
- /* Dropdown: open */
-
- this.$container.on('comboselect:open', $.proxy(this._open, this))
-
-
- /* HTML Click */
-
- $('html').off('click.comboselect').on('click.comboselect', function(){
-
- $.each($.fn[ pluginName ].instances, function(i, plugin){
-
- plugin.$container.trigger('comboselect:close')
-
- })
- });
-
- /* Stop `event:click` bubbling */
-
- this.$container.on('click.comboselect', function(e){
- e.stopPropagation();
- })
-
-
- /* Input: keydown */
-
- this.$container.on('keydown', 'input', $.proxy(this._keydown, this))
-
- /* Input: keyup */
-
- this.$container.on('keyup', 'input', $.proxy(this._keyup, this))
-
- /* Dropdown item: click */
-
- this.$container.on('click.item', '.option-item', $.proxy(this._select, this))
-
- },
-
- _keydown: function(event){
-
-
-
- switch(event.which){
-
- case keys.UP:
- this._move('up', event)
- break;
-
- case keys.DOWN:
- this._move('down', event)
- break;
-
- case keys.TAB:
- this._enter(event)
- break;
-
- case keys.RIGHT:
- this._autofill(event);
- break;
-
- case keys.ENTER:
- this._enter(event);
- break;
-
- default:
- break;
-
-
- }
-
- },
-
-
- _keyup: function(event){
-
- switch(event.which){
- case keys.ESC:
- this.$container.trigger('comboselect:close')
- break;
-
- case keys.ENTER:
- case keys.UP:
- case keys.DOWN:
- case keys.LEFT:
- case keys.RIGHT:
- case keys.TAB:
- case keys.SHIFT:
- break;
-
- default:
- this._filter(event.target.value)
- break;
- }
- },
-
- _enter: function(event){
-
- var item = this._getHovered()
-
- item.length && this._select(item);
-
- /* Check if it enter key */
- if(event && event.which == keys.ENTER){
-
- if(!item.length) {
-
- /* Check if its illegal value */
-
- this._blur();
-
- return true;
- }
-
- event.preventDefault();
- }
-
-
- },
- _move: function(dir){
-
- var items = this._getVisible(),
- current = this._getHovered(),
- index = current.prevAll('.option-item').filter(':visible').length,
- total = items.length
-
-
- switch(dir){
- case 'up':
- index--;
- (index < 0) && (index = (total - 1));
- break;
-
- case 'down':
- index++;
- (index >= total) && (index = 0);
- break;
- }
-
-
- items
- .removeClass(this.settings.hoverClass)
- .eq(index)
- .addClass(this.settings.hoverClass)
-
-
- if(!this.opened) this.$container.trigger('comboselect:open');
-
- this._fixScroll()
- },
-
- _select: function(event){
-
- var item = event.currentTarget? $(event.currentTarget) : $(event);
-
- if(!item.length) return;
-
- /**
- * 1. get Index
- */
-
- var index = item.data('index');
-
- this._selectByIndex(index);
-
- this.$container.trigger('comboselect:close')
-
- },
-
- _selectByIndex: function(index){
-
- /**
- * Set selected index and trigger change
- * @type {[type]}
- */
- if(typeof index == 'undefined'){
-
- index = 0
-
- }
-
- if(this.$el.prop('selectedIndex') != index){
-
- this.$el.prop('selectedIndex', index).trigger('change');
- }
-
- },
-
- _autofill: function(){
-
- var item = this._getHovered();
-
- if(item.length){
-
- var index = item.data('index')
-
- this._selectByIndex(index)
-
- }
-
- },
-
-
- _filter: function(search){
-
- var self = this,
- items = this._getAll();
- needle = $.trim(search).toLowerCase(),
- reEscape = new RegExp('(\\' + ['/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\'].join('|\\') + ')', 'g'),
- pattern = '(' + search.replace(reEscape, '\\$1') + ')';
-
-
- /**
- * Unwrap all markers
- */
-
- $('.'+self.settings.markerClass, items).contents().unwrap();
-
- /* Search */
-
- if(needle){
-
- /* Hide Disabled and optgroups */
-
- this.$items.filter('.option-group, .option-disabled').hide();
-
-
- items
- .hide()
- .filter(function(){
-
- var $this = $(this),
- text = $.trim($this.text()).toLowerCase();
-
- /* Found */
- if(text.toString().indexOf(needle) != -1){
-
- /**
- * Wrap the selection
- */
-
- $this
- .html(function(index, oldhtml){
- return oldhtml.replace(new RegExp(pattern, 'gi'), '<span class="'+self.settings.markerClass+'">$1</span>')
- })
-
- return true
- }
-
- })
- .show()
- }else{
-
-
- this.$items.show();
- }
-
- /* Open the comboselect */
-
- this.$container.trigger('comboselect:open')
-
-
- },
-
- _highlight: function(){
-
- /*
- 1. Check if there is a selected item
- 2. Add hover class to it
- 3. If not add hover class to first item
- */
-
- var visible = this._getVisible().removeClass(this.settings.hoverClass),
- $selected = visible.filter('.'+this.settings.selectedClass)
-
- if($selected.length){
-
- $selected.addClass(this.settings.hoverClass);
-
- }else{
-
- visible
- .removeClass(this.settings.hoverClass)
- .first()
- .addClass(this.settings.hoverClass)
- }
-
- },
-
- _updateInput: function(){
-
- var selected = this.$el.prop('selectedIndex')
-
- if(this.$el.val()){
-
- text = this.$el.find('option').eq(selected).text()
-
- this.$input.val(text)
-
- }else{
-
- this.$input.val('')
-
- }
-
- return this._getAll()
- .removeClass(this.settings.selectedClass)
- .filter(function(){
-
- return $(this).data('index') == selected
- })
- .addClass(this.settings.selectedClass)
-
- },
- _blurSelect: function(){
-
- this.$container.removeClass('combo-focus');
-
- },
- _focus: function(event){
-
- /* Toggle focus class */
-
- this.$container.toggleClass('combo-focus', !this.opened);
-
- /* If mobile: stop */
-
- if(isMobile) return;
-
- /* Open combo */
-
- if(!this.opened) this.$container.trigger('comboselect:open');
-
- /* Select the input */
-
- this.settings.focusInput && event && event.currentTarget && event.currentTarget.nodeName == 'INPUT' && event.currentTarget.select()
- },
-
- _blur: function(){
-
- /**
- * 1. Get hovered item
- * 2. If not check if input value == select option
- * 3. If none
- */
-
- var val = $.trim(this.$input.val().toLowerCase()),
- isNumber = !isNaN(val);
-
- var index = this.$options.filter(function(){
-
- if(isNumber){
- return parseInt($.trim(this.innerHTML).toLowerCase()) == val
- }
-
- return $.trim(this.innerHTML).toLowerCase() == val
-
- }).prop('index')
-
- /* Select by Index */
-
- this._selectByIndex(index)
-
- },
-
- _change: function(){
-
-
- this._updateInput();
-
- },
-
- _getAll: function(){
-
- return this.$items.filter('.option-item')
-
- },
- _getVisible: function(){
-
- return this.$items.filter('.option-item').filter(':visible')
-
- },
-
- _getHovered: function(){
-
- return this._getVisible().filter('.' + this.settings.hoverClass);
-
- },
-
- _open: function(){
-
- var self = this
-
- this.$container.addClass('combo-open')
-
- this.opened = true
-
- /* Focus input field */
-
- this.settings.focusInput && setTimeout(function(){ !self.$input.is(':focus') && self.$input.focus(); });
-
- /* Highligh the items */
-
- this._highlight()
-
- /* Fix scroll */
-
- this._fixScroll()
-
- /* Close all others */
-
-
- $.each($.fn[ pluginName ].instances, function(i, plugin){
-
- if(plugin != self && plugin.opened) plugin.$container.trigger('comboselect:close')
- })
-
- },
-
- _toggle: function(){
-
- this.opened? this._close.call(this) : this._open.call(this)
- },
-
- _close: function(){
-
- this.$container.removeClass('combo-open combo-focus')
-
- this.$container.trigger('comboselect:closed')
-
- this.opened = false
-
- /* Show all items */
-
- this.$items.show();
-
- },
- _fixScroll: function(){
-
- /**
- * If dropdown is hidden
- */
- if(this.$dropdown.is(':hidden')) return;
-
-
- /**
- * Else
- */
- var item = this._getHovered();
-
- if(!item.length) return;
-
- /**
- * Scroll
- */
-
- var offsetTop,
- upperBound,
- lowerBound,
- heightDelta = item.outerHeight()
-
- offsetTop = item[0].offsetTop;
-
- upperBound = this.$dropdown.scrollTop();
-
- lowerBound = upperBound + this.settings.maxHeight - heightDelta;
-
- if (offsetTop < upperBound) {
-
- this.$dropdown.scrollTop(offsetTop);
-
- } else if (offsetTop > lowerBound) {
-
- this.$dropdown.scrollTop(offsetTop - this.settings.maxHeight + heightDelta);
- }
-
- },
- /**
- * Destroy API
- */
-
- dispose: function(){
-
- /* Remove combo arrow, input, dropdown */
-
- this.$arrow.remove()
-
- this.$input.remove()
-
- this.$dropdown.remove()
-
- /* Remove tabindex property */
- this.$el
- .removeAttr("tabindex")
-
- /* Check if there is a tabindex set before */
-
- if(!!this.$el.data('plugin_'+ dataKey + '_tabindex')){
- this.$el.prop('tabindex', this.$el.data('plugin_'+ dataKey + '_tabindex'))
- }
-
- /* Unwrap */
-
- this.$el.unwrap()
-
- /* Remove data */
-
- this.$el.removeData('plugin_'+dataKey)
-
- /* Remove tabindex data */
-
- this.$el.removeData('plugin_'+dataKey + '_tabindex')
-
- /* Remove change event on select */
-
- this.$el.off('change.select focus.select blur.select');
-
- }
- });
-
-
-
- // A really lightweight plugin wrapper around the constructor,
- // preventing against multiple instantiations
- $.fn[ pluginName ] = function ( options, args ) {
-
- this.each(function() {
-
- var $e = $(this),
- instance = $e.data('plugin_'+dataKey)
-
- if (typeof options === 'string') {
-
- if (instance && typeof instance[options] === 'function') {
- instance[options](args);
- }
-
- }else{
-
- if (instance && instance.dispose) {
- instance.dispose();
- }
-
- $.data( this, "plugin_" + dataKey, new Plugin( this, options ) );
-
- }
-
- });
-
- // chain jQuery functions
- return this;
- };
-
- $.fn[ pluginName ].instances = [];
-
- }));
|