From 9a2acb4953f0e26d4d5067562ec827b7c2087441 Mon Sep 17 00:00:00 2001 From: anovi Date: Sun, 12 Jan 2014 22:58:26 +0400 Subject: [PATCH] Updated version and dist folder. --- bower.json | 2 +- dist/selectonic.js | 516 ++++++++++++++++++++++------------------- dist/selectonic.min.js | 4 +- 3 files changed, 274 insertions(+), 248 deletions(-) diff --git a/bower.json b/bower.json index 2eb1d29..bc58bc1 100644 --- a/bower.json +++ b/bower.json @@ -1,7 +1,7 @@ { "name": "selectonic", "title": "Selectonic", - "version": "0.3.1", + "version": "0.4.0", "description": "jQuery-plugin for making any list of items selectable by mouse and keyboard.", "homepage": "https://github.com/anovi/selectonic", "author": { diff --git a/dist/selectonic.js b/dist/selectonic.js index e6ec5aa..cd967be 100644 --- a/dist/selectonic.js +++ b/dist/selectonic.js @@ -1,10 +1,10 @@ -/*! Selectonic - v0.3.1 - 2014-01-02 +/*! Selectonic - v0.4.0 - 2014-01-12 * https://github.com/anovi/selectonic * Copyright (c) 2014 Alexey Novichkov; Licensed MIT */ (function($, window, undefined) { 'use strict'; - // For IE + // For IE compatibility if (typeof Array.prototype.indexOf === 'undefined') { Array.prototype.indexOf = function (searchElement, fromIndex) { if (!this) { throw new TypeError(); } @@ -22,11 +22,8 @@ }; } - // Returns a function, that, when invoked, will only be triggered at most once - // during a given window of time. Normally, the throttled function will run - // as much as it can, without ever going more than once per `wait` duration; - // but if you'd like to disable the execution on the leading edge, pass - // `{leading: false}`. To disable execution on the trailing edge, ditto. + + // From Underscore library – http://underscorejs.org/#throttle var _throttle = function(func, wait, options) { var context, args, result; var timeout = null; @@ -81,23 +78,22 @@ Plugin.pluginName = 'selectonic'; Plugin.keyCode = { DOWN:40, UP:38, SHIFT:16, END:35, HOME:36, PAGE_DOWN:34, PAGE_UP:33, A:65, SPACE:32, ENTER:13 }; Plugin.optionsEvents = ['create','before','focusLost','select','unselect','unselectAll','stop','destroy']; - Plugin.optionsStrings = ['filter','mouseMode','event','keyboardMode','listClass','focusClass','selectedClass','disabledClass','handle']; + Plugin.optionsStrings = ['filter','mouseMode','keyboardMode','listClass','focusClass','selectedClass','disabledClass','handle']; Plugin.defaults = { // Base filter: '> *', multi: true, // Mouse - mouseMode: ['select','toggle'], - event: ['mousedown','click','hybrid'], + mouseMode: ['standard','mouseup','toggle'], focusBlur: false, selectionBlur: false, - handle: null, /* String | null */ + handle: null, textSelection: false, - hoverFocus: false, + focusOnHover: false, // Keyboard keyboard: false, keyboardMode: ['select','toggle'], - autoScroll: true, /* String | false | true */ + autoScroll: true, loop: false, preventInputs: true, // Classes @@ -131,13 +127,13 @@ Plugin.prototype._init = function() { this.$el.addClass( this.options.listClass ); // Add class to box this._bindEvents(); // Attach handlers6 - this.$el.data( 'plugin_' + Plugin.pluginName, this ); // Save plugin's object instance + this.$el.data( 'plugin_' + Plugin.pluginName, this ); // Save plugin's instance this._callEvent('create'); // Callback }; Plugin.prototype._setOptions = function() { - var option, newOptions, isFunction, options = {}, self = this; + var option, newOptions, isFunction, options = {}, _this = this; if ( arguments.length === 2 ) { // First arg is name of option and a second is a value @@ -172,7 +168,7 @@ options[ name ] = $.trim( String(option) ); } // If it's working list and is attempt to change classes - if ( self.options.parentSelector && + if ( _this._itemsSelector && (name === 'listClass' || name === 'focusClass' || name === 'selectedClass' || @@ -194,7 +190,7 @@ newOptions = $.extend( {}, this.options, options ); // Cache items selector to compare it with clicked elements // Plugin's class name + Item selector - newOptions.parentSelector = '.' + newOptions.listClass + ' ' + newOptions.filter; + this._itemsSelector = '.' + newOptions.listClass + ' ' + newOptions.filter; // Set scrollable containter if ( options.autoScroll !== void 0 ) { this._setScrolledElem( options.autoScroll ); } @@ -218,7 +214,7 @@ this.$el.removeClass( this.options.disabledClass ); this.$el.removeClass( this.options.listClass ); delete this._scrolledElem; - delete this._solidInitialElem; + delete this.ui.solidInitialElem; }; @@ -245,22 +241,20 @@ Plugin.prototype._cancel = function( e, params ) { if ( params.wasCancelled ) { return; } params.isCancellation = this._isPrevented = true; + var _this = this; - // Restore items states + // Restores items states for each changed item $.each( - // for each changed item $(params.changedItems), - $.proxy( - function( index, item ) { - // there is boolean value in array prevItemsState - // with same index that item have in _changedItems - if ( params.prevItemsState[ index ] ) { - this._select( e, params, $(item), true ); - } else { - this._unselect( e, params, $(item), true ); - } - }, this - ) + function( index, item ) { + // there is boolean value in array prevItemsStates + // with same index that item has in _changedItems + if ( params.prevItemsStates[ index ] ) { + _this._select( e, params, $(item), true ); + } else { + _this._unselect( e, params, $(item), true ); + } + } ); // Restore old focus if ( params.prevFocus ) { this._setFocus( params.prevFocus ); } @@ -271,7 +265,7 @@ // Attath handlers Plugin.prototype._bindEvents = function() { - var _this = this; + var _this = this, name = this._name; // Handler for mouse events this._mouseEvent = function(e) { @@ -288,46 +282,29 @@ }; // Handler for mousemove this._mousemoveEvent = _throttle( function(e) { - if( _this._isEnable && _this.options.hoverFocus ) { _this._mousemoveHandler.call(_this, e); } + if( _this._isEnable && _this.options.focusOnHover ) { _this._mousemoveHandler.call(_this, e); } }, 20); - $document.on( - 'click' + '.' + this._name + ' ' + 'mousedown' + '.' + this._name, - this._mouseEvent - ); - $document.on( - 'keydown' + '.' + this._name + ' ' + 'keyup' + '.' + this._name, - this._keyboardEvent - ); - this.$el.on( - 'selectstart' + '.' + this._name, - this._selectstartEvent - ); - $document.on( - 'mousemove' + '.' + this._name, - this._mousemoveEvent - ); + $document.on( 'keydown.'+name ,this._keyboardEvent ); + $document.on( 'keyup.'+name ,this._keyboardEvent ); + $document.on( 'mousemove.'+name ,this._mousemoveEvent ); + $document.on( 'click.'+name ,this._mouseEvent ); + $document.on( 'mousedown.'+name ,this._mouseEvent ); + this.$el.on( 'mouseup.'+name ,this._mouseEvent ); + this.$el.on( 'selectstart.'+name ,this._selectstartEvent ); }; // Detach handlers Plugin.prototype._unbindEvents = function() { - $document.off( - 'click' + '.' + this._name + ' ' + 'mousedown' + '.' + this._name, - this._mouseEvent - ); - $document.off( - 'keydown' + '.' + this._name + ' ' + 'keyup' + '.' + this._name, - this._keyboardEvent - ); - this.$el.off( - 'selectstart' + '.' + this._name, - this._selectstartEvent - ); - $document.off( - 'mousemove' + '.' + this._name, - this._mousemoveEvent - ); + var name = this._name; + $document.off( 'keydown.'+name ,this._keyboardEvent ); + $document.off( 'keyup.'+name ,this._keyboardEvent ); + $document.off( 'mousemove.'+name ,this._mousemoveEvent ); + $document.off( 'click.'+name ,this._mouseEvent ); + $document.off( 'mousedown.'+name ,this._mouseEvent ); + this.$el.off( 'mouseup.'+name ,this._mouseEvent ); + this.$el.off( 'selectstart.'+name ,this._selectstartEvent ); }; @@ -343,15 +320,10 @@ $elem = $(elem); // Set context, because old (< 1.10.0) versions of jQuery gives wrong result. $elem.context = window.document; - // If item matches to selector - if( $elem.is(this.options.parentSelector) ) { - target = elem; - } + if( $elem.is(this._itemsSelector) ) { target = elem; } // If handle option is ON and that elem match to handle's selector - if( handle && $elem.is( handle ) ) { - handleElem = elem; - } + if( handle && $elem.is(handle) ) { handleElem = elem; } // Get parent element elem = elem.parentNode; } @@ -370,26 +342,6 @@ }; - Plugin.prototype._getSelected = function( getIds ) { - var arr, res, items; - - if( getIds ) { - arr = []; - items = this.$el.children( '.' + this.options.selectedClass ); - - // Iterate through collection and return id or null - $.each( items, function(index, elem) { - arr.push( $(elem).attr('id') || null ); - }); - res = arr.length > 0 ? arr : null; - - } else { - res = this.$el.children( '.' + this.options.selectedClass ); - } - return res; - }; - - Plugin.prototype._getItems = function( params, target, elem ) { var items; @@ -405,41 +357,13 @@ if ( item.length === 0 ) { break; } // Set context, because old (< 1.10.0) versions of jQuery gives wrong result. item.context = window.document; - if ( item.is(this.options.parentSelector) ) { return item; } + if ( item.is(this._itemsSelector) ) { return item; } } return null; - + case 'pageup': case 'pagedown': - var - box = this._scrolledElem || this.el, - boxViewHeight = box.clientHeight, - winViewHeight = $( window ).outerHeight(), - $current = $( elem ), - isBoxBigger = boxViewHeight > winViewHeight, - pageHeight = isBoxBigger ? winViewHeight : boxViewHeight, - itemHeight = $current.outerHeight(), - currentHeight = itemHeight, - itemsHeight = itemHeight, - direction = (target === 'pageup') ? 'prev' : 'next', - $candidate, candHeight; - - while( true ) { - $candidate = this._getItems( params, direction, $current ); - if ( !$candidate && $current.is( elem ) ) { break; } else if ( !$candidate ) { return $current; } - - candHeight = $candidate.outerHeight(); - itemsHeight = itemsHeight + candHeight; - - if ( itemsHeight > pageHeight ) { - // If two items bigger than page than it just will give next item - if ( currentHeight + candHeight > pageHeight ) { return $candidate; } - return $current; - } - currentHeight = candHeight; - $current = $candidate; - } - return null; + return this._getNextPageElem( params, target, elem); case 'first': items = params.allItems ? params.allItems : this.$el.find( this.options.filter ); @@ -459,6 +383,72 @@ }; + Plugin.prototype._getNextPageElem = function( params, target, elem ) { + /* + * There are two versions of algorithm for searching target depending from page height. + * Page's height is window's or _scrolledElem's height ( which is smaller ). + * Both algorithms runs loop until total item's height reaches maximum possible value, + * but lower than page height. But first version gets from DOM one next element every cycle, + * and second version gets all items at the beginning and then iterates through them. + * And it set allItems and rangeStart and rangeEnd for params. So second version used only + * for Shift+pageUp/Down cases for performance and can be enabled by flag params.isShiftPageRange. + */ + var + _isOptimized = params.isShiftPageRange, + box = this._scrolledElem || this.el, + boxViewHeight = box.clientHeight, + winViewHeight = $( window ).outerHeight(), + $current = $( elem ), + isBoxBigger = boxViewHeight > winViewHeight, + pageHeight = isBoxBigger ? winViewHeight : boxViewHeight, + itemHeight = $current.outerHeight(), + currentHeight = itemHeight, + itemsHeight = itemHeight, + direction = (target === 'pageup') ? 'prev' : 'next', + $candidate, candHeight, currentIndex, allItems, cand; + + if ( _isOptimized ) { + direction = (target === 'pageup') ? -1 : 1; + allItems = this._getItems( params ); + params.rangeStart = currentIndex = allItems.index( elem ); + } + + while( true ) { + if ( _isOptimized ) { + currentIndex = currentIndex + direction; + cand = currentIndex >= 0 ? allItems.eq( currentIndex ) : null; + $candidate = cand && cand.length > 0 ? cand : null; + } else { + $candidate = this._getItems( params, direction, $current ); + } + + if ( !$candidate && $current.is( elem ) ) { + break; + } else if ( !$candidate ) { + if ( _isOptimized ) { params.rangeEnd = currentIndex - direction; } + return $current; + } + + candHeight = $candidate.outerHeight(); + itemsHeight = itemsHeight + candHeight; + + if ( itemsHeight > pageHeight ) { + // If two items bigger than page than it just will give next item + if ( currentHeight + candHeight > pageHeight ) { + if ( _isOptimized ) { params.rangeEnd = currentIndex; } + return $candidate; + } + + if ( _isOptimized ) { params.rangeEnd = currentIndex - direction; } + return $current; + } + currentHeight = candHeight; + $current = $candidate; + } + return null; + }; + + // Creates ui object and calls a callback from the options Plugin.prototype._callEvent = function( name, event, params ) { var ui, cb = this.options[name]; @@ -481,13 +471,13 @@ }; - // Control the state of a list - // this method calls from _keyHandler and _mouseHandler or API - // and do changes depending from passed params + // Control the state of a list. + // It can be called from _keyHandler, _mouseHandler or API + // and does list's changes depending from reseived params. Plugin.prototype._controller = function( e, params ) { var method; params.changedItems = []; - params.prevItemsState = []; + params.prevItemsStates = []; delete this._isPrevented; this._callEvent('before', e, params); @@ -583,12 +573,12 @@ this._unselect( e, params, items ); this._select( e, params, params.items ); - // Existing Solid selection and target not selected - // And initial selection elem is in current range + // Existing Solid selection and target is not selected + // and initial selection's elem is in current range } else if ( - this._solidInitialElem && + this.ui.solidInitialElem && !params.isTargetWasSelected && - (initial = params.items.index( this._solidInitialElem )) >= 0 + (initial = params.items.index( this.ui.solidInitialElem )) >= 0 ) { // Need to unselect items from start to initial elem and select from initial elem to the end initial = ( endAfterStart ) ? params.rangeStart + initial : params.rangeEnd + initial; @@ -628,13 +618,13 @@ var aboveZero = delta > 0, changedItems = [], - self = this; + _this = this; // For each of items calls function in scope plugin's object instance $( items ).each( function( index, item ) { var - isSelected = self._getIsSelected( item ), + isSelected = _this._getIsSelected( item ), // Condition - if item is not selected (_select) or items is selected (_unselect) selectedCondition = ( aboveZero ) ? !isSelected : isSelected, // if the item is target and is selected @@ -644,24 +634,19 @@ and is not 'multi' or 'range' select mode — do nothing because state of selected target should not change – it is just unselecting other items */ - if ( - isSelectedTarget && - !aboveZero && - !params.isMultiSelect && - !params.isRangeSelect - ) { return; } + if (isSelectedTarget && !aboveZero && !params.isMultiSelect && !params.isRangeSelect ) { return; } if( selectedCondition ) { // it is not cancellation if( !params.isCancellation ) { changedItems.push( item ); - params.prevItemsState.push( isSelected ); + params.prevItemsStates.push( isSelected ); } - self._selected += delta; + _this._selected += delta; } // Finally add/remove class to item - $( item ).toggleClass( self.options.selectedClass, aboveZero ); + $( item ).toggleClass( _this.options.selectedClass, aboveZero ); }); @@ -715,7 +700,7 @@ if( params.target === this.ui.focus ) { return $( params.target ); } // Detect position of target and focus in the list - var arr = this._getItems( params ), + var arr = params.allItems ? params.allItems : this._getItems( params ), x = arr.index( params.target ), y = arr.index( this.ui.focus ), @@ -723,6 +708,7 @@ subArr = ( x < y ) ? arr.slice( x, y ) : arr.slice( y, x ); subArr.push( ( x < y ) ? arr[ y ] : arr[ x ] ); + params.allItems = arr; params.rangeStart = y; params.rangeEnd = x; return subArr; @@ -777,6 +763,31 @@ if( this._isPrevented ) { this._cancel( e, params ); } }; + + Plugin.prototype._checkIfElem = function( selector ) { + var res; + if ( selector && (selector.jquery || selector.nodeType) ) { + // Filter received elements through cached selecter + selector = selector.jquery ? selector : $( selector ); + res = selector.filter( this._itemsSelector ); + return res.length > 0 ? res : null; + + } else { return false; } + }; + + + Plugin.prototype._checkIfSelector = function( selector ) { + var res; + // Test for selector + if ( selector && typeof selector === 'string') { + res = this.$el + .find( selector ) // Try to find + .filter( this._itemsSelector ); // Filter found resents + return ( res.jquery && res.length > 0 ) ? res : null; + + } else { return false; } + }; + /* ============================================================================== @@ -788,12 +799,7 @@ if ( !this.options.keyboard ) { return; } if ( this.options.preventInputs && e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') { return; } - - var key = e.which, // pressed key - params = {}, - target, // sibling element - isAllSelect, // flag that is all items is selected - direction; // next or previous (depends from pressed arrow up|down) + var key = e.which, params = {}, target, isAllSelect, direction, page; // Key is released if (e.type === 'keyup') { @@ -804,21 +810,13 @@ return; } // If CTRL+A or CMD+A pressed and multi option is true - if ( key === Plugin.keyCode.A && (e.metaKey || e.ctrlKey) && this.options.multi ) { + if ( key === Plugin.keyCode.A && this._isMulti(e) && this.options.multi ) { target = this._getItems( params ); - isAllSelect = true; + isAllSelect = true; // flag that is all items is selected } else { // Choose direction and try to find targeted item switch ( key ) { - case Plugin.keyCode.HOME: - direction = 'prev'; - target = this._getItems( params, 'first'); - break; - case Plugin.keyCode.END: - direction = 'next'; - target = this._getItems( params, 'last'); - break; case Plugin.keyCode.DOWN: direction = 'next'; target = this._findNextTarget( 'next', params ); @@ -827,13 +825,21 @@ direction = 'prev'; target = this._findNextTarget( 'prev', params ); break; - case Plugin.keyCode.PAGE_DOWN: + case Plugin.keyCode.HOME: + direction = 'prev'; + target = this._getItems( params, 'first'); + break; + case Plugin.keyCode.END: direction = 'next'; - target = this._findNextTarget( 'pagedown', params ); + target = this._getItems( params, 'last'); break; + case Plugin.keyCode.PAGE_DOWN: case Plugin.keyCode.PAGE_UP: - direction = 'prev'; - target = this._findNextTarget( 'pageup', params ); + var isDown = key === Plugin.keyCode.PAGE_DOWN; + direction = isDown ? 'next' : 'prev'; + page = isDown ? 'pagedown' : 'pageup'; + params.isShiftPageRange = this.options.multi && e.shiftKey && !isAllSelect; + target = this._findNextTarget( page, params ); break; case Plugin.keyCode.SPACE: target = $( this.ui.focus ); @@ -861,15 +867,10 @@ delete params.items; } if ( this.options.multi ) { params.isMultiSelect = true; } - delete this._solidInitialElem; + delete this.ui.solidInitialElem; // SHIFT mode - } else if ( - this.ui.focus && - this.options.multi && - e.shiftKey && - !isAllSelect - ) { + } else if ( this.ui.focus && this.options.multi && e.shiftKey && !isAllSelect ) { // Call multiVariator or rangeVariator – // it set all needed params depends from arguments if ( @@ -882,8 +883,8 @@ } // Set solid selection - if ( !this._solidInitialElem && params.target !== this.ui.focus ) { - this._solidInitialElem = this.ui.focus; + if ( !this.ui.solidInitialElem && params.target !== this.ui.focus ) { + this.ui.solidInitialElem = this.ui.focus; params.isNewSolidSelection = true; } @@ -892,12 +893,10 @@ if ( !this._keyModes.shift ) { this._keyModes.shift = key; } } else { - delete this._solidInitialElem; + delete this.ui.solidInitialElem; } - // There are all necessary attributes now this._controller( e, params ); - // Recalculate plugin's box and window's scrolls this.scroll(); } @@ -907,7 +906,7 @@ Plugin.prototype._rangeVariator = function( params ) { var - isFocusSelected = this._getIsSelected( this.ui.focus ), + isFocusSelected = void 0 === params.isFocusSelected ? this._getIsSelected( this.ui.focus ) : params.isFocusSelected, isTargetSelected = params.isTargetWasSelected = this._getIsSelected( params.target ); if ( !isFocusSelected && !isTargetSelected ) { @@ -934,7 +933,7 @@ Plugin.prototype._multiVariator = function( params, key, direction, target ) { var // Check if focus or target is selected - isFocusSelected = this._getIsSelected( this.ui.focus ), + isFocusSelected = void 0 === params.isFocusSelected ? this._getIsSelected( this.ui.focus ) : params.isFocusSelected, isTargetSelected = this._getIsSelected( params.target ), // Search for next target in the same direction afterTarget = this._getItems( params, direction, target ), @@ -986,8 +985,7 @@ /* Used by _keyHandler - when UP or DOWN keys was pressed — find next item or first/last of the list - direction – next|prev + when UP, DOWN, PageUp, PageDown keys has pressed — find target or first/last element of the list */ Plugin.prototype._findNextTarget = function( direction, params ) { var edge = ( direction === 'next' || direction === "pagedown" ) ? 'first' : 'last', // extreme item of the list @@ -1032,6 +1030,16 @@ }; + Plugin.prototype._isRange = function( e ) { + return e.shiftKey || (e.shiftKey && e.ctrlKey) || (e.shiftKey && e.metaKey); + }; + + + Plugin.prototype._isMulti = function( e ) { + return e.ctrlKey || e.metaKey; + }; + + /* ============================================================================== @@ -1040,68 +1048,81 @@ */ // Mouse events handler - set necessary paramaters and calls _controller Plugin.prototype._mouseHandler = function( e ) { - var options = this.options, - params = {}; - - // If hybrid mode - if ( options.event === 'hybrid' ) { - - // It is click and mouse was not pressed on item - if ( e.type === 'click' && !this._mouseDownMode ) { return; } - - params.target = this._getTarget(e); - - if ( params.target && e.type === 'mousedown' ) { + var + options = this.options, + type = e.type, + isMulti = this._isMulti(e), + isRange = this._isRange(e), + params = {}, + target; + + /* Find target: */ + // mouseup mode + if (options.mouseMode === 'mouseup') { + if (type === 'mouseup') { + target = this._getTarget(e); + + // if mousedown event was at the item then do nothing + } else if ( type === 'mousedown' || (target = this._getTarget(e)) ) { + return; + + } else { return; } + + // because this click may be after mousedown in multi/range mode + } else if (type === 'click' && !this._mousedownOnItem) { + return; - params.isTargetWasSelected = this._getIsSelected( params.target ); - if ( params.isTargetWasSelected ) { - this._mouseDownMode = true; - return; - } + // mousedown and click only + } else if (type === 'mousedown' || type === 'click') { + target = this._getTarget(e); + // Mousedown on item + // Except cases mathes all conditions: + // - in multi/range modes + // - with multi:true + // - with mouseMode:'standard' + if (type === 'mousedown' && target && !( options.multi && (isMulti||isRange) && options.mouseMode === 'standard' )) { + this._mousedownOnItem = target; + return; } + delete this._mousedownOnItem; + } else { return; } - // If mouse down mode - if ( this._mouseDownMode ) { delete this._mouseDownMode; } - - // if type of event do not match - } else if ( e.type !== options.event ) { - return; - - // Get target - } else { params.target = this._getTarget(e); } + params.target = target; // If multi options is true and target exists if( options.multi && params.target ) { // Range select - if ( (e.shiftKey || e.shiftKey && e.ctrlKey) && this.ui.focus ) { + if ( isRange && this.ui.focus ) { params.items = this._rangeSelect( params ); // Add/subtract to selection - } else if( e.ctrlKey || e.metaKey || options.mouseMode === 'toggle' ) { + } else if ( isMulti || options.mouseMode === 'toggle' ) { params.items = this._multiSelect( params ); } } if ( params.target && !params.items ) { params.items = $( params.target ); } - delete this._solidInitialElem; + delete this.ui.solidInitialElem; this._controller( e, params ); }; // Tries to find target under cursor when mouse moves Plugin.prototype._mousemoveHandler = function( e ) { - if ( this._isHoverFocusPrevented ) { return; } + if ( this._isFocusOnHoverPrevented ) { return; } var params = {}, target; target = this._getTarget( e ); - if (target) { - delete this._solidInitialElem; + if ( target ) { + delete this.ui.solidInitialElem; + this._isHovered = true; if ( target !== this.ui.focus ) { params.target = target; this._controller( e, params ); } - } else { + } else if ( this._isHovered ) { + this._isHovered = false; this._controller( e, params ); } }; @@ -1111,7 +1132,7 @@ // and list's element changes scroll position Plugin.prototype._preventMouseMove = function() { var _this = this; - this._isHoverFocusPrevented = true; + this._isFocusOnHoverPrevented = true; if ( this._focusHoverTimeout ) { clearTimeout( this._focusHoverTimeout ); @@ -1119,7 +1140,7 @@ } this._focusHoverTimeout = setTimeout( function() { - delete _this._isHoverFocusPrevented; + delete _this._isFocusOnHoverPrevented; delete _this._focusHoverTimeout; }, 250); }; @@ -1195,59 +1216,63 @@ Plugin.prototype.select = function( selector ) { - var $elem, params = {}; + var $elem; - // If received DOM $element - if ( selector && (selector.jquery || selector.nodeType) ) { - // Filter received elements through cached selecter - selector = selector.jquery ? selector : $( selector ); - $elem = selector.filter( this.options.parentSelector ); - $elem = $elem.length > 0 ? $elem : null; - - } else if (typeof selector === 'string') { - // Test for selector - $elem = this.$el - .find( selector ) // Try to find - .filter( this.options.parentSelector ); // Filter found $elements - $elem = ( $elem.jquery && $elem.length > 0 ) ? $elem : null; - } else { + $elem = this._checkIfElem( selector ); + if ( $elem === false) { $elem = this._checkIfSelector( selector ); } + if ( $elem === false) { throw new Error( 'You shold pass DOM element or selector to \"select\" method.' ); } - if ( $elem ) { - // Set params for _controller method: - params.items = ( $elem.addClass ) ? $elem : $( $elem ); - params.target = $elem[0] || $elem; - - // Call _controller with null instead of event object - delete this._solidInitialElem; - this._controller( null, params ); + delete this.ui.solidInitialElem; + this._controller( null, { + items: ( $elem.addClass ) ? $elem : $( $elem ), + target: $elem[0] || $elem + }); } return this.$el; }; Plugin.prototype.blur = function() { - var params = {}; - // If target is not exist, _blur will be called - params.target = null; // Call _controller with null instead of event object - this._controller( null, params ); + this._controller( null, { target: null } ); return this.$el; }; - Plugin.prototype.getSelected = function() { - return this._getSelected(); + Plugin.prototype.getSelected = function( getIds ) { + var arr, + items = this._getItems({}).filter( '.' + this.options.selectedClass ); + + if( getIds ) { + arr = []; + for (var i = 0; i < items.length; i++) { arr.push(items[i].id || null); } + return (arr && arr.length > 0) ? arr : null; + } + return items; }; Plugin.prototype.getSelectedId = function() { - return this._getSelected( true ); + return this.getSelected( true ); }; - Plugin.prototype.getFocused = function() { + Plugin.prototype.focus = function( selector ) { + var $elem; + + if ( arguments.length > 0 ) { + $elem = ($elem = this._checkIfElem( selector )) === false ? this._checkIfSelector( selector ) : $elem; + if ( $elem && $elem.jquery ) { + this._setFocus( $elem[0] ); + + } else if ( $elem === false) { + throw new Error( 'You shold pass DOM element or CSS selector to set focus or nothing to get it.' ); + } + return this.$el; + } + if (this.ui.focus) { return this.ui.focus; } else { return null; } }; @@ -1270,6 +1295,7 @@ Plugin.prototype.disable = function() { this._isEnable = false; + this._isHovered = false; this.$el.addClass( this.options.disabledClass ); return this.$el; }; diff --git a/dist/selectonic.min.js b/dist/selectonic.min.js index 4ac3532..175c7c2 100644 --- a/dist/selectonic.min.js +++ b/dist/selectonic.min.js @@ -1,4 +1,4 @@ -/*! Selectonic - v0.3.1 - 2014-01-02 +/*! Selectonic - v0.4.0 - 2014-01-12 * https://github.com/anovi/selectonic * Copyright (c) 2014 Alexey Novichkov; Licensed MIT */ -!function(a,b,c){"use strict";function d(b,c){this._name=d.pluginName,this.el=b,this.$el=a(b),this.ui={},this._selected=0,this._isEnable=!0,this._keyModes={},this.options={};var e=a.extend({},d.defaults,c||{});this._setOptions(e),this._init()}"undefined"==typeof Array.prototype.indexOf&&(Array.prototype.indexOf=function(a,b){if(!this)throw new TypeError;b=isNaN(b=+b)?0:b;var c=this.length;if(0===c||b>=c)return-1;for(0>b&&(b+=c);c>b;){if(this[b]===a)return b;++b}return-1});var e=function(a,b,c){var d,e,f,g=null,h=0;c=c||{};var i=function(){h=c.leading===!1?0:new Date,g=null,f=a.apply(d,e)};return function(){var j=new Date;h||c.leading!==!1||(h=j);var k=b-(j-h);return d=this,e=arguments,0>=k?(clearTimeout(g),g=null,h=j,f=a.apply(d,e)):g||c.trailing===!1||(g=setTimeout(i,k)),f}},f=a(b.document);d.pluginName="selectonic",d.keyCode={DOWN:40,UP:38,SHIFT:16,END:35,HOME:36,PAGE_DOWN:34,PAGE_UP:33,A:65,SPACE:32,ENTER:13},d.optionsEvents=["create","before","focusLost","select","unselect","unselectAll","stop","destroy"],d.optionsStrings=["filter","mouseMode","event","keyboardMode","listClass","focusClass","selectedClass","disabledClass","handle"],d.defaults={filter:"> *",multi:!0,mouseMode:["select","toggle"],event:["mousedown","click","hybrid"],focusBlur:!1,selectionBlur:!1,handle:null,textSelection:!1,hoverFocus:!1,keyboard:!1,keyboardMode:["select","toggle"],autoScroll:!0,loop:!1,preventInputs:!0,listClass:"j-selectable",focusClass:"j-focused",selectedClass:"j-selected",disabledClass:"j-disabled",create:null,before:null,focusLost:null,select:null,unselect:null,unselectAll:null,stop:null,destroy:null},d.getDataObject=function(b){return a(b).data("plugin_"+d.pluginName)},d.prototype._init=function(){this.$el.addClass(this.options.listClass),this._bindEvents(),this.$el.data("plugin_"+d.pluginName,this),this._callEvent("create")},d.prototype._setOptions=function(){var b,c,e,f={},g=this;if(2===arguments.length)f[arguments[0]]=arguments[1];else{if(!a.isPlainObject(f))throw new Error('Format of "option" could be: "option" or "option","name" or "option","name",val or "option",{}');f=arguments[0]}a.each(d.optionsStrings,function(c,e){if(b=f[e]){var h=["mouseMode","event","keyboardMode"].indexOf(e);if(a.isArray(b)&&h>=0&&b===d.defaults[e])f[e]=b[0];else if(h>=0){var i=d.defaults[e];if(i.indexOf(a.trim(String(b)))<0)throw new RangeError('Option "'+e+'" only could be in these values: "'+i.join('", "')+'".')}else f[e]=a.trim(String(b));if(g.options.parentSelector&&("listClass"===e||"focusClass"===e||"selectedClass"===e||"disabledClass"===e))throw new Error("Sorry, it's not allowed to dynamically change classnames!")}}),a.each(d.optionsEvents,function(c,d){if(b=f[d],void 0!==b&&(e=a.isFunction(b),!e&&null!==b))throw new TypeError('Option "'+d+'" should be a function or "null"!')}),c=a.extend({},this.options,f),c.parentSelector="."+c.listClass+" "+c.filter,void 0!==f.autoScroll&&this._setScrolledElem(f.autoScroll),this.options=c},d.prototype._destroy=function(){this._callEvent("destroy"),this._unbindEvents(),this._focusHoverTimeout&&clearTimeout(this._focusHoverTimeout),this.ui.focus&&(a(this.ui.focus).removeClass(this.options.focusClass),delete this.ui.focus),this._selected>0&&this.getSelected().removeClass(this.options.selectedClass),this.$el.removeClass(this.options.disabledClass),this.$el.removeClass(this.options.listClass),delete this._scrolledElem,delete this._solidInitialElem},d.prototype._setScrolledElem=function(b){var c;if(null===b||!1===b)return delete this._scrolledElem,void 0;if("string"==typeof b){if(c=a(b),!(c.length>0))throw new Error('There are no elements that matches to selector - "'+b+'"');return this._scrolledElem=c[0],void 0}this._scrolledElem=this.el},d.prototype._cancel=function(b,c){c.wasCancelled||(c.isCancellation=this._isPrevented=!0,a.each(a(c.changedItems),a.proxy(function(d,e){c.prevItemsState[d]?this._select(b,c,a(e),!0):this._unselect(b,c,a(e),!0)},this)),c.prevFocus&&this._setFocus(c.prevFocus),delete c.isCancellation,c.wasCancelled=!0)},d.prototype._bindEvents=function(){var a=this;this._mouseEvent=function(b){return a._isEnable&&a._mouseHandler.call(a,b),b},this._keyboardEvent=function(b){a.options.keyboard&&a._isEnable&&a._keyHandler.call(a,b)},this._selectstartEvent=function(){return a.options.textSelection?void 0:!1},this._mousemoveEvent=e(function(b){a._isEnable&&a.options.hoverFocus&&a._mousemoveHandler.call(a,b)},20),f.on("click."+this._name+" mousedown."+this._name,this._mouseEvent),f.on("keydown."+this._name+" keyup."+this._name,this._keyboardEvent),this.$el.on("selectstart."+this._name,this._selectstartEvent),f.on("mousemove."+this._name,this._mousemoveEvent)},d.prototype._unbindEvents=function(){f.off("click."+this._name+" mousedown."+this._name,this._mouseEvent),f.off("keydown."+this._name+" keyup."+this._name,this._keyboardEvent),this.$el.off("selectstart."+this._name,this._selectstartEvent),f.off("mousemove."+this._name,this._mousemoveEvent)},d.prototype._getTarget=function(c){for(var d,e,f,g=c.target,h=this.options.handle;null!==g&&g!==this.el;)d=a(g),d.context=b.document,d.is(this.options.parentSelector)&&(e=g),h&&d.is(h)&&(f=g),g=g.parentNode;return h&&g&&f?e:!h&&g?e:null},d.prototype._getSelected=function(b){var c,d,e;return b?(c=[],e=this.$el.children("."+this.options.selectedClass),a.each(e,function(b,d){c.push(a(d).attr("id")||null)}),d=c.length>0?c:null):d=this.$el.children("."+this.options.selectedClass),d},d.prototype._getItems=function(c,d,e){var f;switch(d){case"next":case"prev":for(var g=e.jquery?e:a(e),h=a.fn[d];;){if(g=h.call(g),0===g.length)break;if(g.context=b.document,g.is(this.options.parentSelector))return g}return null;case"pageup":case"pagedown":for(var i,j,k=this._scrolledElem||this.el,l=k.clientHeight,m=a(b).outerHeight(),n=a(e),o=l>m,p=o?m:l,q=n.outerHeight(),r=q,s=q,t="pageup"===d?"prev":"next";;){if(i=this._getItems(c,t,n),!i&&n.is(e))break;if(!i)return n;if(j=i.outerHeight(),s+=j,s>p)return r+j>p?i:n;r=j,n=i}return null;case"first":return f=c.allItems?c.allItems:this.$el.find(this.options.filter),c.allItems=f,f.first();case"last":return f=c.allItems?c.allItems:this.$el.find(this.options.filter),c.allItems=f,f.last();default:return f=c.allItems?c.allItems:this.$el.find(this.options.filter),c.allItems=f,f}},d.prototype._callEvent=function(a,b,c){var d,e=this.options[a];if(e){if("create"===a||"destroy"===a)return e.call(this.$el);switch(d={},c.target&&(d.target=c.target),this.ui.focus&&(d.focus=this.ui.focus),a){case"select":d.items=c.selected;break;case"unselectAll":case"unselect":d.items=c.unselected;break;case"stop":c.wasCancelled||(d.items=c.changedItems)}e.call(this.$el,b||null,d)}},d.prototype._controller=function(a,b){var d;return b.changedItems=[],b.prevItemsState=[],delete this._isPrevented,this._callEvent("before",a,b),this._isPrevented?(this._cancel(a,b),this._stop(a,b),void 0):(b.wasSelected=this._selected>0,b.target&&b.isTargetWasSelected===c&&(b.isTargetWasSelected=this._getIsSelected(b.target)),b.isRangeSelect&&b.isTargetWasSelected&&b.target===this.ui.focus||(b.isRangeSelect?this._perfomRangeSelect(a,b):b.isMultiSelect?(d=b.isTargetWasSelected?this._unselect:this._select,d.call(this,a,b,b.items)):b.target&&!b.items&&"mouseover"===a.type||(b.target&&b.items?(this._selected&&1===this._selected&&this._getIsSelected(this.ui.focus)?this._unselect(a,b,this.ui.focus,b.isTargetWasSelected):this._selected&&this._unselectAll(a,b),this._select(a,b,b.items,b.isTargetWasSelected)):!b.target&&this._selected>0&&this.options.selectionBlur&&this._unselectAll(a,b))),!this._selected&&b.wasSelected&&this._callEvent("unselectAll",a,b),b.prevFocus=this.ui.focus?this.ui.focus:null,!b.target&&this.options.focusBlur?this._blur(a,b):b.target&&!b.wasCancelled&&this._setFocus(b.target),this._stop(a,b),void 0)},d.prototype._perfomRangeSelect=function(a,b){var c,d,e,f,g,h,i,j=b.rangeStart=0?(e=j?b.rangeStart+e:b.rangeEnd+e,f=e0&&this._unselect(a,b,d)),(i&&!g||h&&!f)&&(d=i?k.slice(l,e):k.slice(e+1,m+1),d.length>0&&this._select(a,b,d))):(c=b.isTargetWasSelected?this._unselect:this._select,c.call(this,a,b,b.items))},d.prototype._changeItemsStates=function(b,c,d){var e=c>0,f=[],g=this;a(b).each(function(b,h){var i=g._getIsSelected(h),j=e?!i:i,k=h===d.target&&d.isTargetWasSelected;(!k||e||d.isMultiSelect||d.isRangeSelect)&&(j&&(d.isCancellation||(f.push(h),d.prevItemsState.push(i)),g._selected+=c),a(h).toggleClass(g.options.selectedClass,e))}),d.isCancellation||(d[e?"selected":"unselected"]=a(f),d.changedItems=d.changedItems.concat(f))},d.prototype._select=function(a,b,c,d){this._changeItemsStates(c,1,b),d||this._callEvent("select",a,b),this._isPrevented&&!b.isCancellation&&this._cancel(a,b)},d.prototype._unselect=function(a,b,c,d){this._changeItemsStates(c,-1,b),d||this._callEvent("unselect",a,b),this._isPrevented&&!b.isCancellation&&this._cancel(a,b)},d.prototype._unselectAll=function(a,b){var c,d;this._selected&&0!==this._selected&&(d=this._getItems(b),c=b.target&&b.isTargetWasSelected&&1===this._selected,this._unselect(a,b,d,c))},d.prototype._multiSelect=function(b){return b.isMultiSelect=!0,a(b.target)},d.prototype._rangeSelect=function(b){if(b.isRangeSelect=!0,b.target===this.ui.focus)return a(b.target);var c=this._getItems(b),d=c.index(b.target),e=c.index(this.ui.focus),f=e>d?c.slice(d,e):c.slice(e,d);return f.push(e>d?c[e]:c[d]),b.rangeStart=e,b.rangeEnd=d,f},d.prototype._getIsSelected=function(b){var c=this.options;return a(b).length<=1?a(b).hasClass(c.selectedClass):a.map(a(b),function(b){return a(b).hasClass(c.selectedClass)})},d.prototype._blur=function(b,c,d){!d&&this.ui.focus&&this._callEvent("focusLost",b,c),this.ui.focus&&(a(this.ui.focus).removeClass(this.options.focusClass),delete this.ui.focus)},d.prototype._setFocus=function(b){return b?(this.ui.focus&&a(this.ui.focus).removeClass(this.options.focusClass),this.ui.focus=b,a(this.ui.focus).addClass(this.options.focusClass),this.ui.focus):void 0},d.prototype._stop=function(a,b){this._callEvent("stop",a,b),this._isPrevented&&this._cancel(a,b)},d.prototype._keyHandler=function(b){if(this.options.keyboard&&!(this.options.preventInputs&&"INPUT"===b.target.tagName||"TEXTAREA"===b.target.tagName)){var c,e,f,g=b.which,h={};if("keyup"===b.type)return g===d.keyCode.SHIFT&&(delete this._shiftModeAction,delete this._keyModes.shift),void 0;if(g===d.keyCode.A&&(b.metaKey||b.ctrlKey)&&this.options.multi)c=this._getItems(h),e=!0;else switch(g){case d.keyCode.HOME:f="prev",c=this._getItems(h,"first");break;case d.keyCode.END:f="next",c=this._getItems(h,"last");break;case d.keyCode.DOWN:f="next",c=this._findNextTarget("next",h);break;case d.keyCode.UP:f="prev",c=this._findNextTarget("prev",h);break;case d.keyCode.PAGE_DOWN:f="next",c=this._findNextTarget("pagedown",h);break;case d.keyCode.PAGE_UP:f="prev",c=this._findNextTarget("pageup",h);break;case d.keyCode.SPACE:c=a(this.ui.focus);break;case d.keyCode.ENTER:this.options.multi||(c=a(this.ui.focus))}return c&&c.length>0&&(b.preventDefault(),h.target=c[0],h.items=c,"toggle"===this.options.keyboardMode?(g===d.keyCode.SPACE||g===d.keyCode.ENTER&&!this.options.multi||delete h.items,this.options.multi&&(h.isMultiSelect=!0),delete this._solidInitialElem):this.ui.focus&&this.options.multi&&b.shiftKey&&!e?(g===d.keyCode.END||g===d.keyCode.HOME||g===d.keyCode.PAGE_UP||g===d.keyCode.PAGE_DOWN?this._rangeVariator(h):this._multiVariator(h,g,f,c),this._solidInitialElem||h.target===this.ui.focus||(this._solidInitialElem=this.ui.focus,h.isNewSolidSelection=!0),this._shiftModeAction||(this._shiftModeAction="select"),this._keyModes.shift||(this._keyModes.shift=g)):delete this._solidInitialElem,this._controller(b,h),this.scroll()),b}},d.prototype._rangeVariator=function(a){var b=this._getIsSelected(this.ui.focus),c=a.isTargetWasSelected=this._getIsSelected(a.target);b||c?(a.items=this._rangeSelect(a),c&&(a.items=a.rangeStart0;)e=a.items,a.items=this._getItems(a,c,a.items);a.target=a.items?a.items:e}else g&&f&&!i?(this._keyModes.shift=this._shiftModeAction=null,a.items=this.ui.focus):f&&g?(a.items=this.ui.focus,this._shiftModeAction||(this._shiftModeAction="unselect")):f||(a.target=a.items=this.ui.focus);a.isMultiSelect=!0},d.prototype._findNextTarget=function(a,b){var c="next"===a||"pagedown"===a?"first":"last",d=this.ui.focus?this._getItems(b,a,this.ui.focus):this._getItems(b,c);return null!==d&&0!==d.length||!this.options.loop||(d=this._getItems(b,c)),d},d.prototype._refreshBoxScroll=function(c){var d=a(c),e=c===b,f=e?d.outerHeight():c.clientHeight,g=d.scrollTop(),h=e?0:d.offset().top,i=a(this.ui.focus),j=i.outerHeight(),k=e?i.offset().top:i.offset().top-h+g;g>k?d.scrollTop(k):k+j>g+f&&d.scrollTop(k+j-f)},d.prototype._mouseHandler=function(b){var c=this.options,d={};if("hybrid"===c.event){if("click"===b.type&&!this._mouseDownMode)return;if(d.target=this._getTarget(b),d.target&&"mousedown"===b.type&&(d.isTargetWasSelected=this._getIsSelected(d.target),d.isTargetWasSelected))return this._mouseDownMode=!0,void 0;this._mouseDownMode&&delete this._mouseDownMode}else{if(b.type!==c.event)return;d.target=this._getTarget(b)}c.multi&&d.target&&((b.shiftKey||b.shiftKey&&b.ctrlKey)&&this.ui.focus?d.items=this._rangeSelect(d):(b.ctrlKey||b.metaKey||"toggle"===c.mouseMode)&&(d.items=this._multiSelect(d))),d.target&&!d.items&&(d.items=a(d.target)),delete this._solidInitialElem,this._controller(b,d)},d.prototype._mousemoveHandler=function(a){if(!this._isHoverFocusPrevented){var b,c={};b=this._getTarget(a),b?(delete this._solidInitialElem,b!==this.ui.focus&&(c.target=b,this._controller(a,c))):this._controller(a,c)}},d.prototype._preventMouseMove=function(){var a=this;this._isHoverFocusPrevented=!0,this._focusHoverTimeout&&(clearTimeout(this._focusHoverTimeout),delete this._focusHoverTimeout),this._focusHoverTimeout=setTimeout(function(){delete a._isHoverFocusPrevented,delete a._focusHoverTimeout},250)},d._callPublicMethod=function(b){var c,e,f=d.getDataObject(this);if(null===f||void 0===f)throw new Error("Element "+this[0]+" has no plugin "+d.pluginName);if(f[b]&&a.isFunction(f[b])&&(c=f[b]),c&&a.isFunction(c)&&"_"!==b.charAt(0))return e=Array.prototype.slice.call(arguments),e.shift(),c.apply(f,e);throw new Error('Plugin "'+d.pluginName+'" has no method "'+b+'"')},d.prototype.isEnabled=function(){return this._isEnable},d.prototype.option=function(b,c){var d=arguments.length;if(d>0&&"string"==typeof b)return d>1?(this._setOptions(b,c),this.$el):this.options[b];if(d>0&&a.isPlainObject(b))return this._setOptions(b),this.$el;if(0===d)return a.extend({},this.options);throw new Error('Format of "option" could be: "option" or "option","name" or "option","name",val or "option",{}')},d.prototype.destroy=function(){this._destroy(),this.$el.removeData("plugin_"+d.pluginName),this.$el=null},d.prototype.select=function(b){var c,d={};if(b&&(b.jquery||b.nodeType))b=b.jquery?b:a(b),c=b.filter(this.options.parentSelector),c=c.length>0?c:null;else{if("string"!=typeof b)throw new Error('You shold pass DOM element or selector to "select" method.');c=this.$el.find(b).filter(this.options.parentSelector),c=c.jquery&&c.length>0?c:null}return c&&(d.items=c.addClass?c:a(c),d.target=c[0]||c,delete this._solidInitialElem,this._controller(null,d)),this.$el},d.prototype.blur=function(){var a={};return a.target=null,this._controller(null,a),this.$el},d.prototype.getSelected=function(){return this._getSelected()},d.prototype.getSelectedId=function(){return this._getSelected(!0)},d.prototype.getFocused=function(){return this.ui.focus?this.ui.focus:null},d.prototype.scroll=function(){this._preventMouseMove(),this.ui.focus&&(this._scrolledElem&&this._refreshBoxScroll(this._scrolledElem),this._refreshBoxScroll(b))},d.prototype.enable=function(){return this._isEnable=!0,this.$el.removeClass(this.options.disabledClass),this.$el},d.prototype.disable=function(){return this._isEnable=!1,this.$el.addClass(this.options.disabledClass),this.$el},d.prototype.cancel=function(){return this._isPrevented=!0,this.$el},d.prototype.refresh=function(){var b=this.ui.focus;return b&&!a(b).is(":visible")&&delete this.ui.focus,this._selected=this.getSelected().length,this.$el},a.fn[d.pluginName]=function(a){return a&&a.charAt?d._callPublicMethod.apply(this,arguments):this.each(function(b,c){d.getDataObject(c)||new d(c,a)})}}(jQuery,window); \ No newline at end of file +!function(a,b,c){"use strict";function d(b,c){this._name=d.pluginName,this.el=b,this.$el=a(b),this.ui={},this._selected=0,this._isEnable=!0,this._keyModes={},this.options={};var e=a.extend({},d.defaults,c||{});this._setOptions(e),this._init()}"undefined"==typeof Array.prototype.indexOf&&(Array.prototype.indexOf=function(a,b){if(!this)throw new TypeError;b=isNaN(b=+b)?0:b;var c=this.length;if(0===c||b>=c)return-1;for(0>b&&(b+=c);c>b;){if(this[b]===a)return b;++b}return-1});var e=function(a,b,c){var d,e,f,g=null,h=0;c=c||{};var i=function(){h=c.leading===!1?0:new Date,g=null,f=a.apply(d,e)};return function(){var j=new Date;h||c.leading!==!1||(h=j);var k=b-(j-h);return d=this,e=arguments,0>=k?(clearTimeout(g),g=null,h=j,f=a.apply(d,e)):g||c.trailing===!1||(g=setTimeout(i,k)),f}},f=a(b.document);d.pluginName="selectonic",d.keyCode={DOWN:40,UP:38,SHIFT:16,END:35,HOME:36,PAGE_DOWN:34,PAGE_UP:33,A:65,SPACE:32,ENTER:13},d.optionsEvents=["create","before","focusLost","select","unselect","unselectAll","stop","destroy"],d.optionsStrings=["filter","mouseMode","keyboardMode","listClass","focusClass","selectedClass","disabledClass","handle"],d.defaults={filter:"> *",multi:!0,mouseMode:["standard","mouseup","toggle"],focusBlur:!1,selectionBlur:!1,handle:null,textSelection:!1,focusOnHover:!1,keyboard:!1,keyboardMode:["select","toggle"],autoScroll:!0,loop:!1,preventInputs:!0,listClass:"j-selectable",focusClass:"j-focused",selectedClass:"j-selected",disabledClass:"j-disabled",create:null,before:null,focusLost:null,select:null,unselect:null,unselectAll:null,stop:null,destroy:null},d.getDataObject=function(b){return a(b).data("plugin_"+d.pluginName)},d.prototype._init=function(){this.$el.addClass(this.options.listClass),this._bindEvents(),this.$el.data("plugin_"+d.pluginName,this),this._callEvent("create")},d.prototype._setOptions=function(){var b,c,e,f={},g=this;if(2===arguments.length)f[arguments[0]]=arguments[1];else{if(!a.isPlainObject(f))throw new Error('Format of "option" could be: "option" or "option","name" or "option","name",val or "option",{}');f=arguments[0]}a.each(d.optionsStrings,function(c,e){if(b=f[e]){var h=["mouseMode","event","keyboardMode"].indexOf(e);if(a.isArray(b)&&h>=0&&b===d.defaults[e])f[e]=b[0];else if(h>=0){var i=d.defaults[e];if(i.indexOf(a.trim(String(b)))<0)throw new RangeError('Option "'+e+'" only could be in these values: "'+i.join('", "')+'".')}else f[e]=a.trim(String(b));if(g._itemsSelector&&("listClass"===e||"focusClass"===e||"selectedClass"===e||"disabledClass"===e))throw new Error("Sorry, it's not allowed to dynamically change classnames!")}}),a.each(d.optionsEvents,function(c,d){if(b=f[d],void 0!==b&&(e=a.isFunction(b),!e&&null!==b))throw new TypeError('Option "'+d+'" should be a function or "null"!')}),c=a.extend({},this.options,f),this._itemsSelector="."+c.listClass+" "+c.filter,void 0!==f.autoScroll&&this._setScrolledElem(f.autoScroll),this.options=c},d.prototype._destroy=function(){this._callEvent("destroy"),this._unbindEvents(),this._focusHoverTimeout&&clearTimeout(this._focusHoverTimeout),this.ui.focus&&(a(this.ui.focus).removeClass(this.options.focusClass),delete this.ui.focus),this._selected>0&&this.getSelected().removeClass(this.options.selectedClass),this.$el.removeClass(this.options.disabledClass),this.$el.removeClass(this.options.listClass),delete this._scrolledElem,delete this.ui.solidInitialElem},d.prototype._setScrolledElem=function(b){var c;if(null===b||!1===b)return delete this._scrolledElem,void 0;if("string"==typeof b){if(c=a(b),!(c.length>0))throw new Error('There are no elements that matches to selector - "'+b+'"');return this._scrolledElem=c[0],void 0}this._scrolledElem=this.el},d.prototype._cancel=function(b,c){if(!c.wasCancelled){c.isCancellation=this._isPrevented=!0;var d=this;a.each(a(c.changedItems),function(e,f){c.prevItemsStates[e]?d._select(b,c,a(f),!0):d._unselect(b,c,a(f),!0)}),c.prevFocus&&this._setFocus(c.prevFocus),delete c.isCancellation,c.wasCancelled=!0}},d.prototype._bindEvents=function(){var a=this,b=this._name;this._mouseEvent=function(b){return a._isEnable&&a._mouseHandler.call(a,b),b},this._keyboardEvent=function(b){a.options.keyboard&&a._isEnable&&a._keyHandler.call(a,b)},this._selectstartEvent=function(){return a.options.textSelection?void 0:!1},this._mousemoveEvent=e(function(b){a._isEnable&&a.options.focusOnHover&&a._mousemoveHandler.call(a,b)},20),f.on("keydown."+b,this._keyboardEvent),f.on("keyup."+b,this._keyboardEvent),f.on("mousemove."+b,this._mousemoveEvent),f.on("click."+b,this._mouseEvent),f.on("mousedown."+b,this._mouseEvent),this.$el.on("mouseup."+b,this._mouseEvent),this.$el.on("selectstart."+b,this._selectstartEvent)},d.prototype._unbindEvents=function(){var a=this._name;f.off("keydown."+a,this._keyboardEvent),f.off("keyup."+a,this._keyboardEvent),f.off("mousemove."+a,this._mousemoveEvent),f.off("click."+a,this._mouseEvent),f.off("mousedown."+a,this._mouseEvent),this.$el.off("mouseup."+a,this._mouseEvent),this.$el.off("selectstart."+a,this._selectstartEvent)},d.prototype._getTarget=function(c){for(var d,e,f,g=c.target,h=this.options.handle;null!==g&&g!==this.el;)d=a(g),d.context=b.document,d.is(this._itemsSelector)&&(e=g),h&&d.is(h)&&(f=g),g=g.parentNode;return h&&g&&f?e:!h&&g?e:null},d.prototype._getItems=function(c,d,e){var f;switch(d){case"next":case"prev":for(var g=e.jquery?e:a(e),h=a.fn[d];;){if(g=h.call(g),0===g.length)break;if(g.context=b.document,g.is(this._itemsSelector))return g}return null;case"pageup":case"pagedown":return this._getNextPageElem(c,d,e);case"first":return f=c.allItems?c.allItems:this.$el.find(this.options.filter),c.allItems=f,f.first();case"last":return f=c.allItems?c.allItems:this.$el.find(this.options.filter),c.allItems=f,f.last();default:return f=c.allItems?c.allItems:this.$el.find(this.options.filter),c.allItems=f,f}},d.prototype._getNextPageElem=function(c,d,e){var f,g,h,i,j,k=c.isShiftPageRange,l=this._scrolledElem||this.el,m=l.clientHeight,n=a(b).outerHeight(),o=a(e),p=m>n,q=p?n:m,r=o.outerHeight(),s=r,t=r,u="pageup"===d?"prev":"next";for(k&&(u="pageup"===d?-1:1,i=this._getItems(c),c.rangeStart=h=i.index(e));;){if(k?(h+=u,j=h>=0?i.eq(h):null,f=j&&j.length>0?j:null):f=this._getItems(c,u,o),!f&&o.is(e))break;if(!f)return k&&(c.rangeEnd=h-u),o;if(g=f.outerHeight(),t+=g,t>q)return s+g>q?(k&&(c.rangeEnd=h),f):(k&&(c.rangeEnd=h-u),o);s=g,o=f}return null},d.prototype._callEvent=function(a,b,c){var d,e=this.options[a];if(e){if("create"===a||"destroy"===a)return e.call(this.$el);switch(d={},c.target&&(d.target=c.target),this.ui.focus&&(d.focus=this.ui.focus),a){case"select":d.items=c.selected;break;case"unselectAll":case"unselect":d.items=c.unselected;break;case"stop":c.wasCancelled||(d.items=c.changedItems)}e.call(this.$el,b||null,d)}},d.prototype._controller=function(a,b){var d;return b.changedItems=[],b.prevItemsStates=[],delete this._isPrevented,this._callEvent("before",a,b),this._isPrevented?(this._cancel(a,b),this._stop(a,b),void 0):(b.wasSelected=this._selected>0,b.target&&b.isTargetWasSelected===c&&(b.isTargetWasSelected=this._getIsSelected(b.target)),b.isRangeSelect&&b.isTargetWasSelected&&b.target===this.ui.focus||(b.isRangeSelect?this._perfomRangeSelect(a,b):b.isMultiSelect?(d=b.isTargetWasSelected?this._unselect:this._select,d.call(this,a,b,b.items)):b.target&&!b.items&&"mouseover"===a.type||(b.target&&b.items?(this._selected&&1===this._selected&&this._getIsSelected(this.ui.focus)?this._unselect(a,b,this.ui.focus,b.isTargetWasSelected):this._selected&&this._unselectAll(a,b),this._select(a,b,b.items,b.isTargetWasSelected)):!b.target&&this._selected>0&&this.options.selectionBlur&&this._unselectAll(a,b))),!this._selected&&b.wasSelected&&this._callEvent("unselectAll",a,b),b.prevFocus=this.ui.focus?this.ui.focus:null,!b.target&&this.options.focusBlur?this._blur(a,b):b.target&&!b.wasCancelled&&this._setFocus(b.target),this._stop(a,b),void 0)},d.prototype._perfomRangeSelect=function(a,b){var c,d,e,f,g,h,i,j=b.rangeStart=0?(e=j?b.rangeStart+e:b.rangeEnd+e,f=e0&&this._unselect(a,b,d)),(i&&!g||h&&!f)&&(d=i?k.slice(l,e):k.slice(e+1,m+1),d.length>0&&this._select(a,b,d))):(c=b.isTargetWasSelected?this._unselect:this._select,c.call(this,a,b,b.items))},d.prototype._changeItemsStates=function(b,c,d){var e=c>0,f=[],g=this;a(b).each(function(b,h){var i=g._getIsSelected(h),j=e?!i:i,k=h===d.target&&d.isTargetWasSelected;(!k||e||d.isMultiSelect||d.isRangeSelect)&&(j&&(d.isCancellation||(f.push(h),d.prevItemsStates.push(i)),g._selected+=c),a(h).toggleClass(g.options.selectedClass,e))}),d.isCancellation||(d[e?"selected":"unselected"]=a(f),d.changedItems=d.changedItems.concat(f))},d.prototype._select=function(a,b,c,d){this._changeItemsStates(c,1,b),d||this._callEvent("select",a,b),this._isPrevented&&!b.isCancellation&&this._cancel(a,b)},d.prototype._unselect=function(a,b,c,d){this._changeItemsStates(c,-1,b),d||this._callEvent("unselect",a,b),this._isPrevented&&!b.isCancellation&&this._cancel(a,b)},d.prototype._unselectAll=function(a,b){var c,d;this._selected&&0!==this._selected&&(d=this._getItems(b),c=b.target&&b.isTargetWasSelected&&1===this._selected,this._unselect(a,b,d,c))},d.prototype._multiSelect=function(b){return b.isMultiSelect=!0,a(b.target)},d.prototype._rangeSelect=function(b){if(b.isRangeSelect=!0,b.target===this.ui.focus)return a(b.target);var c=b.allItems?b.allItems:this._getItems(b),d=c.index(b.target),e=c.index(this.ui.focus),f=e>d?c.slice(d,e):c.slice(e,d);return f.push(e>d?c[e]:c[d]),b.allItems=c,b.rangeStart=e,b.rangeEnd=d,f},d.prototype._getIsSelected=function(b){var c=this.options;return a(b).length<=1?a(b).hasClass(c.selectedClass):a.map(a(b),function(b){return a(b).hasClass(c.selectedClass)})},d.prototype._blur=function(b,c,d){!d&&this.ui.focus&&this._callEvent("focusLost",b,c),this.ui.focus&&(a(this.ui.focus).removeClass(this.options.focusClass),delete this.ui.focus)},d.prototype._setFocus=function(b){return b?(this.ui.focus&&a(this.ui.focus).removeClass(this.options.focusClass),this.ui.focus=b,a(this.ui.focus).addClass(this.options.focusClass),this.ui.focus):void 0},d.prototype._stop=function(a,b){this._callEvent("stop",a,b),this._isPrevented&&this._cancel(a,b)},d.prototype._checkIfElem=function(b){var c;return b&&(b.jquery||b.nodeType)?(b=b.jquery?b:a(b),c=b.filter(this._itemsSelector),c.length>0?c:null):!1},d.prototype._checkIfSelector=function(a){var b;return a&&"string"==typeof a?(b=this.$el.find(a).filter(this._itemsSelector),b.jquery&&b.length>0?b:null):!1},d.prototype._keyHandler=function(b){if(this.options.keyboard&&!(this.options.preventInputs&&"INPUT"===b.target.tagName||"TEXTAREA"===b.target.tagName)){var c,e,f,g,h=b.which,i={};if("keyup"===b.type)return h===d.keyCode.SHIFT&&(delete this._shiftModeAction,delete this._keyModes.shift),void 0;if(h===d.keyCode.A&&this._isMulti(b)&&this.options.multi)c=this._getItems(i),e=!0;else switch(h){case d.keyCode.DOWN:f="next",c=this._findNextTarget("next",i);break;case d.keyCode.UP:f="prev",c=this._findNextTarget("prev",i);break;case d.keyCode.HOME:f="prev",c=this._getItems(i,"first");break;case d.keyCode.END:f="next",c=this._getItems(i,"last");break;case d.keyCode.PAGE_DOWN:case d.keyCode.PAGE_UP:var j=h===d.keyCode.PAGE_DOWN;f=j?"next":"prev",g=j?"pagedown":"pageup",i.isShiftPageRange=this.options.multi&&b.shiftKey&&!e,c=this._findNextTarget(g,i);break;case d.keyCode.SPACE:c=a(this.ui.focus);break;case d.keyCode.ENTER:this.options.multi||(c=a(this.ui.focus))}return c&&c.length>0&&(b.preventDefault(),i.target=c[0],i.items=c,"toggle"===this.options.keyboardMode?(h===d.keyCode.SPACE||h===d.keyCode.ENTER&&!this.options.multi||delete i.items,this.options.multi&&(i.isMultiSelect=!0),delete this.ui.solidInitialElem):this.ui.focus&&this.options.multi&&b.shiftKey&&!e?(h===d.keyCode.END||h===d.keyCode.HOME||h===d.keyCode.PAGE_UP||h===d.keyCode.PAGE_DOWN?this._rangeVariator(i):this._multiVariator(i,h,f,c),this.ui.solidInitialElem||i.target===this.ui.focus||(this.ui.solidInitialElem=this.ui.focus,i.isNewSolidSelection=!0),this._shiftModeAction||(this._shiftModeAction="select"),this._keyModes.shift||(this._keyModes.shift=h)):delete this.ui.solidInitialElem,this._controller(b,i),this.scroll()),b}},d.prototype._rangeVariator=function(a){var b=void 0===a.isFocusSelected?this._getIsSelected(this.ui.focus):a.isFocusSelected,c=a.isTargetWasSelected=this._getIsSelected(a.target);b||c?(a.items=this._rangeSelect(a),c&&(a.items=a.rangeStart0;)e=a.items,a.items=this._getItems(a,c,a.items);a.target=a.items?a.items:e}else g&&f&&!i?(this._keyModes.shift=this._shiftModeAction=null,a.items=this.ui.focus):f&&g?(a.items=this.ui.focus,this._shiftModeAction||(this._shiftModeAction="unselect")):f||(a.target=a.items=this.ui.focus);a.isMultiSelect=!0},d.prototype._findNextTarget=function(a,b){var c="next"===a||"pagedown"===a?"first":"last",d=this.ui.focus?this._getItems(b,a,this.ui.focus):this._getItems(b,c);return null!==d&&0!==d.length||!this.options.loop||(d=this._getItems(b,c)),d},d.prototype._refreshBoxScroll=function(c){var d=a(c),e=c===b,f=e?d.outerHeight():c.clientHeight,g=d.scrollTop(),h=e?0:d.offset().top,i=a(this.ui.focus),j=i.outerHeight(),k=e?i.offset().top:i.offset().top-h+g;g>k?d.scrollTop(k):k+j>g+f&&d.scrollTop(k+j-f)},d.prototype._isRange=function(a){return a.shiftKey||a.shiftKey&&a.ctrlKey||a.shiftKey&&a.metaKey},d.prototype._isMulti=function(a){return a.ctrlKey||a.metaKey},d.prototype._mouseHandler=function(b){var c,d=this.options,e=b.type,f=this._isMulti(b),g=this._isRange(b),h={};if("mouseup"===d.mouseMode){if("mouseup"!==e)return"mousedown"===e||(c=this._getTarget(b))?void 0:void 0;c=this._getTarget(b)}else{if("click"===e&&!this._mousedownOnItem)return;if("mousedown"!==e&&"click"!==e)return;if(c=this._getTarget(b),"mousedown"===e&&c&&(!d.multi||!f&&!g||"standard"!==d.mouseMode))return this._mousedownOnItem=c,void 0;delete this._mousedownOnItem}h.target=c,d.multi&&h.target&&(g&&this.ui.focus?h.items=this._rangeSelect(h):(f||"toggle"===d.mouseMode)&&(h.items=this._multiSelect(h))),h.target&&!h.items&&(h.items=a(h.target)),delete this.ui.solidInitialElem,this._controller(b,h)},d.prototype._mousemoveHandler=function(a){if(!this._isFocusOnHoverPrevented){var b,c={};b=this._getTarget(a),b?(delete this.ui.solidInitialElem,this._isHovered=!0,b!==this.ui.focus&&(c.target=b,this._controller(a,c))):this._isHovered&&(this._isHovered=!1,this._controller(a,c))}},d.prototype._preventMouseMove=function(){var a=this;this._isFocusOnHoverPrevented=!0,this._focusHoverTimeout&&(clearTimeout(this._focusHoverTimeout),delete this._focusHoverTimeout),this._focusHoverTimeout=setTimeout(function(){delete a._isFocusOnHoverPrevented,delete a._focusHoverTimeout},250)},d._callPublicMethod=function(b){var c,e,f=d.getDataObject(this);if(null===f||void 0===f)throw new Error("Element "+this[0]+" has no plugin "+d.pluginName);if(f[b]&&a.isFunction(f[b])&&(c=f[b]),c&&a.isFunction(c)&&"_"!==b.charAt(0))return e=Array.prototype.slice.call(arguments),e.shift(),c.apply(f,e);throw new Error('Plugin "'+d.pluginName+'" has no method "'+b+'"')},d.prototype.isEnabled=function(){return this._isEnable},d.prototype.option=function(b,c){var d=arguments.length;if(d>0&&"string"==typeof b)return d>1?(this._setOptions(b,c),this.$el):this.options[b];if(d>0&&a.isPlainObject(b))return this._setOptions(b),this.$el;if(0===d)return a.extend({},this.options);throw new Error('Format of "option" could be: "option" or "option","name" or "option","name",val or "option",{}')},d.prototype.destroy=function(){this._destroy(),this.$el.removeData("plugin_"+d.pluginName),this.$el=null},d.prototype.select=function(b){var c;if(c=this._checkIfElem(b),c===!1&&(c=this._checkIfSelector(b)),c===!1)throw new Error('You shold pass DOM element or selector to "select" method.');return c&&(delete this.ui.solidInitialElem,this._controller(null,{items:c.addClass?c:a(c),target:c[0]||c})),this.$el},d.prototype.blur=function(){return this._controller(null,{target:null}),this.$el},d.prototype.getSelected=function(a){var b,c=this._getItems({}).filter("."+this.options.selectedClass);if(a){b=[];for(var d=0;d0?b:null}return c},d.prototype.getSelectedId=function(){return this.getSelected(!0)},d.prototype.focus=function(a){var b;if(arguments.length>0){if(b=(b=this._checkIfElem(a))===!1?this._checkIfSelector(a):b,b&&b.jquery)this._setFocus(b[0]);else if(b===!1)throw new Error("You shold pass DOM element or CSS selector to set focus or nothing to get it.");return this.$el}return this.ui.focus?this.ui.focus:null},d.prototype.scroll=function(){this._preventMouseMove(),this.ui.focus&&(this._scrolledElem&&this._refreshBoxScroll(this._scrolledElem),this._refreshBoxScroll(b))},d.prototype.enable=function(){return this._isEnable=!0,this.$el.removeClass(this.options.disabledClass),this.$el},d.prototype.disable=function(){return this._isEnable=!1,this._isHovered=!1,this.$el.addClass(this.options.disabledClass),this.$el},d.prototype.cancel=function(){return this._isPrevented=!0,this.$el},d.prototype.refresh=function(){var b=this.ui.focus;return b&&!a(b).is(":visible")&&delete this.ui.focus,this._selected=this.getSelected().length,this.$el},a.fn[d.pluginName]=function(a){return a&&a.charAt?d._callPublicMethod.apply(this,arguments):this.each(function(b,c){d.getDataObject(c)||new d(c,a)})}}(jQuery,window); \ No newline at end of file