(function($, window, undefined) {
  'use strict'; // remove this line, this script will be support running on IE6

  // Method for getting full elements height, jQuery's outerHeight is like Zepto's height
  var outerHeight = $.fn.outerHeight ? 'outerHeight' : 'height';
  if ( !$.fn.jquery && !$.fn.zepto ) { $.fn.zepto = true; }

  // From Underscore library - http://underscorejs.org/#throttle
  var _throttle = function(func, wait, options) {
    var context, args, result;
    var timeout = null;
    var previous = 0;
    options = options || {};
    var later = function() {
      previous = options.leading === false ? 0 : new Date();
      timeout = null;
      result = func.apply(context, args);
    };
    return function() {
      var now = new Date();
      if (!previous && options.leading === false) { previous = now; }
      var remaining = wait - (now - previous);
      context = this;
      args = arguments;
      if (remaining <= 0) {
        clearTimeout(timeout);
        timeout = null;
        previous = now;
        result = func.apply(context, args);
      } else if (!timeout && options.trailing !== false) {
        timeout = setTimeout(later, remaining);
      }
      return result;
    };
  },

  __indexOf = Array.prototype.indexOf || function(item) {
    for (var i = 0, l = this.length; i < l; i++) { if (this[i] === item) {return i;}  }
    return -1;
  },

  itContains = function( array, elem ) {
    if (array instanceof Array) { return __indexOf.call(array, elem) >= 0; }
    return false;
  },

  $document = $( window.document );

  /**
  * @class Options
  * @constructor
  * @for Selectonic
  * @param {Object} schema Options schema.
  * @param {Object} initial Initial options, will be mixed with schema's defaults.
  **/ 
  function Options ( schema, defaults, initial ) {
    this._schema    = schema;
    this._options   = {};
    this._callbacks = {};
    this.set( $.extend({}, defaults, initial||{}), true );
    return this;
  }

  Options.isCorrectType = function(val, schema) {
    var type = typeof val, isNullable = val === null && schema.nullable;
    return ( schema.type instanceof Array ) ? itContains(schema.type, type) || isNullable : type === schema.type || isNullable;
  };

  Options.prototype.set = function( obj, isNew ) {
    var option, callback;

    // Check options
    for ( option in obj ) {
      var val = obj[ option ],
      schema = this._schema[ option ];

      if ( schema !== undefined ) {
        // unchangeable
        if ( schema.unchangeable && !isNew ) {
          throw new Error( 'Option \"' + option + '\" could be setted once at the begining.' );
        }
        // wrong type
        if ( !Options.isCorrectType(val, schema) ) {
          var msg = 'Option \"' + option + '\" must be ' +
            ( schema.type instanceof Array ? schema.type.join(', ') : schema.type ) +
            ( schema.nullable ? ' or null.' : '.' );
          throw new TypeError( msg );
        }
        // out of values
        if ( schema.values && !itContains(schema.values, val) ) {
          throw new RangeError( 'Option \"' + option + '\" only could be in these values: \"' + schema.values.join('\", \"') + '\".' );
        }
      }
    }
    // Callbacks
    for ( option in obj ) {
      if ( (callback = this._callbacks[option]) ) {
        obj[option] = callback.call( this, obj[option] );
      }
    }
    this._options = $.extend( this._options, obj );
  };

  Options.prototype.get = function( opt ) {
    return opt ? this._options[ opt ] : $.extend( {}, this._options );
  };

  Options.prototype.on = function( option, cb ) {
    this._callbacks[ option ] = cb;
  };

  Options.prototype.off = function( option ) {
    if ( this._callbacks[ option ] ) { delete this._callbacks[option]; }
  };


  var defaults = {
    filter:         '> *',
    multi:          true,
    // Mouse
    mouseMode:      'standard',
    focusBlur:      false,
    selectionBlur:  false,
    handle:         null,
    textSelection:  false,
    focusOnHover:   false,
    // Keyboard
    keyboard:       false,
    keyboardMode:   'select',
    autoScroll:     true,
    loop:           false,
    preventInputs:  true,
    // Classes
    listClass:      'j-selectable',
    focusClass:     'j-focused',
    selectedClass:  'j-selected',
    disabledClass:  'j-disabled',
    // Callbacks
    create:         null,
    before:         null,
    focusLost:      null,
    select:         null,
    unselect:       null,
    unselectAll:    null,
    stop:           null,
    destroy:        null
  },
  schema = {
    filter:         { type:'string'                                         },
    multi:          { type:'boolean'                                        },
    // Mouse
    mouseMode:      { type:'string', values:['standard','mouseup','toggle'] },
    focusBlur:      { type:'boolean'                                        },
    selectionBlur:  { type:'boolean'                                        },
    handle:         { type:'string', nullable:true                          },
    textSelection:  { type:'boolean'                                        },
    focusOnHover:   { type:'boolean'                                        },
    // Keyboard
    keyboard:       { type:'boolean'                                        },
    keyboardMode:   { type:'string', values:['select','toggle'],            },
    autoScroll:     { type:['boolean','string']                             },
    loop:           { type:'boolean'                                        },
    preventInputs:  { type:'boolean'                                        },
    // Classes
    listClass:      { type:'string', unchangeable:true                      },
    focusClass:     { type:'string', unchangeable:true                      },
    selectedClass:  { type:'string', unchangeable:true                      },
    disabledClass:  { type:'string', unchangeable:true                      },
    // Callbacks
    create:         { type:'function', nullable:true                        },
    before:         { type:'function', nullable:true                        },
    focusLost:      { type:'function', nullable:true                        },
    select:         { type:'function', nullable:true                        },
    unselect:       { type:'function', nullable:true                        },
    unselectAll:    { type:'function', nullable:true                        },
    stop:           { type:'function', nullable:true                        },
    destroy:        { type:'function', nullable:true                        }
  };


  /**
  * @class Selectonic
  * @constructor
  * @param {HTMLElement} element
  * @param {Object} options will be mixed with default options
  **/ 
  function Plugin( element, options ) {
    this._name      = Plugin.pluginName;
    this.el         = element;
    this.$el        = $( element );
    this.ui         = {};   // Object for DOM elements
    this._selected  = 0;    // Amount of selected items
    this._isEnable  = true; // Flag that plugin is enabled - used by handlers
    this._keyModes  = {};   // to saving holding keys
    this.options    = new Options( schema, defaults, options );

    var _this = this;
    this.options.on('filter', function( value ) {
      // Cache items selector to compare it with clicked elements
      _this._itemsSelector = '.' + _this.options.get('listClass') + ' ' + value;
      return value;
    });
    this.options.on('autoScroll', function( value ) {
      _this._setScrolledElem( value ); // Set scrollable containter
      return value;
    });
    this._itemsSelector = '.' + this.options.get('listClass') + ' ' + this.options.get('filter');
    this._setScrolledElem( this.options.get('autoScroll') );
    this._init();
  }

  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 };

  
  /**
  * Gets plugin's data object
  * @param {HTMLElement} el
  * @method getDataObject
  * @static
  * @private
  * @return {Object} Selectonic instance.
  **/ 
  Plugin.getDataObject = function( el ) {
    return $( el ).data( 'plugin_' + Plugin.pluginName );
  };



  /* ==============================================================================

  Core

  */
  /**
  * Initialisation method.
  * @method _init
  * @private
  **/
  Plugin.prototype._init = function() {
    this.$el.addClass( this.options.get('listClass') );   // Add class to box
    this._bindEvents();                                   // Attach handlers6
    this.$el.data( 'plugin_' + Plugin.pluginName, this ); // Save plugin's instance
    this._trigger('create');                              // Callback
  };


  /**
  * Set scrollable container.
  * @method _setScrolledElem
  * @private
  * @param {String|HTMLElement} selector Element or selector as scrollable elem.
  **/
  Plugin.prototype._setScrolledElem = function( selector ) {
    var elem;

    if ( null === selector || false === selector ) {
      delete this._scrolledElem;
      return;
    }
    if ( typeof selector === "string" ) {
      elem = $( selector );
      if (elem.length > 0) {
        this._scrolledElem = elem[0];
      } else {
        throw new Error('There are no elements that matches to selector - \"' + selector + '\"');
      }
      return;
    }
    this._scrolledElem = this.el;
  };


  /**
  * Cancel list changes in the current cycle or prevent them
  * if called in `before` callback.
  * @method _cancel
  * @private
  * @param {Object} event Event object.
  * @param {Object} params Current params.
  **/
  Plugin.prototype._cancel = function( e, params ) {
    if ( params.wasCancelled ) { return; }
    params.isCancellation = this._isPrevented = true;
    var _this = this;

    // Restores items states for each changed item
    $.each(
      $(params.changedItems),
      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 ); }
    delete params.isCancellation;
    params.wasCancelled = true;
  };

  
  /**
  * Attath handlers for plugin instace.
  * @method _bindEvents
  * @private
  **/
  Plugin.prototype._bindEvents = function() {
    var _this = this, name = this._name;

    this._mouseEvent = function(e) {
      if ( _this._isEnable && e.which === 1) { _this._mouseHandler.call(_this, e); }
    };
    this._keyboardEvent = function(e) {
      if( _this.options.get('keyboard') && _this._isEnable ) { _this._keyHandler.call(_this, e); }
    };
    this._selectstartEvent = function() {
      if ( !_this.options.get('textSelection') ) { return false; }
    };
    this._mousemoveEvent = _throttle( function(e) {
      if( _this._isEnable && _this.options && _this.options.get('focusOnHover') ) { _this._mousemoveHandler.call(_this, e); }
    }, 20);

    $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       );
    $document.on( 'mouseup.'+name       ,this._mouseEvent       );
    this.$el.on(  'selectstart.'+name   ,this._selectstartEvent );
  };

  
  /**
  * Detach instance handlers.
  * @method _unbindEvents
  * @private
  **/
  Plugin.prototype._unbindEvents = function() {
    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       );
    $document.off( 'mouseup.'+name       ,this._mouseEvent       );
    this.$el.off(  'selectstart.'+name   ,this._selectstartEvent );
  };


  /**
  * Gets item, that was clicked
  * or null, if click was not on an item
  * @method _getTarget
  * @private
  * @param {Object} event Event object.
  * @return {HTMLElement|null} Targeted element if it has found of null.
  **/
  Plugin.prototype._getTarget = function( e ) {
    var elem = e.target,
      handle = this.options.get('handle'),
      $elem, target, handleElem;

    // While plugin's element or top of the DOM is achieved
    while ( elem !== null && elem !== this.el ) {
      $elem = $(elem);
      // Set context, because old (< 1.10.0) versions of jQuery gives wrong result.
      $elem.context = window.document;
      if( $elem.is(this._itemsSelector) ) { target = elem; }
      if( handle && $elem.is(handle) ) { handleElem = elem; }
      elem = elem.parentNode;
    }
    if( handle && elem && handleElem ) {
      return target;

    // If achieved $el of this instance of plugin's object
    } else if( !handle && elem ) {
      return target;
    }
    // has not clicked any selectable items of a list
    return null; 
  };


  /**
  * Getter for list's items.
  * @method _getItems
  * @private
  * @param {Object} params Current params.
  * @param {String} [target] Find 'next' 'prev' 'pageup' 'pagedown' item 
  *   relative to `elem` argument or 'first' or 'last' item of the list.
  * @param {HTMLElement} [elem] Element.
  * @return {jQuery object|null} Found element wrapped in jQuery or null.
  **/
  Plugin.prototype._getItems = function( params, target, elem ) {
    var items;

    switch( target ) {
    case 'next':
    case 'prev':
      var
      item = elem.jquery ? elem : $( elem ),
      find = $.fn[target];

      while (true) {
        item = find.call( item );
        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._itemsSelector) ) { return item; }
      }
      return null;

    case 'pageup':
    case 'pagedown':
      return this._getNextPageElem( params, target, elem);

    case 'first':
      items = params.allItems ? params.allItems : this.$el.find( this.options.get('filter') );
      params.allItems = items;
      return items.first();

    case 'last':
      items = params.allItems ? params.allItems : this.$el.find( this.options.get('filter') );
      params.allItems = items;
      return items.last();

    default:
      items = params.allItems ? params.allItems : this.$el.find( this.options.get('filter') );
      params.allItems = items;
      return items;
    }
  };


  /**
  * Used by _getItems
  * 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.
  *
  * @method _getNextPageElem
  * @private
  * @param {Object} params Current params.
  * @param {String} target Find 'pageup' 'pagedown' item relative to `elem` argument.
  * @param {HTMLElement} elem Element.
  * @return {jQuery object|null} Found element wrapped in jQuery or null.
  **/ 
  Plugin.prototype._getNextPageElem = function( params, target, elem ) {
    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[0] === 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;
  };


  /**
  * Calls callbacks functions from options if it exists:
  * - Creates ui object with target, focus and changed items
  * - Passess to callback event or null and ui object
  * @method _trigger
  * @private
  * @param {String} name Name of callback.
  * @param {Object|null} event Event object
  * @param {Object} params Current params.
  **/
  Plugin.prototype._trigger = function( name, event, params ) {
    var ui, cb = this.options.get(name);
    if ( !cb ) { return; }
    if ( name === 'create' || name === 'destroy' ) {
      return cb.call( this.$el );
    }
    ui = {};
    if ( params.target ) { ui.target = params.target; }
    if ( this.ui.focus ) { ui.focus  = this.ui.focus; }

    switch ( name ) {
      case 'select':      ui.items = params.selected; break;
      case 'unselectAll':
      case 'unselect':    ui.items = params.unselected; break;
      case 'stop':        if ( !params.wasCancelled ) { ui.items = params.changedItems; } break;
    }
    // Pass to callback: elem, event object and new ui object
    cb.call( this.$el, event || null, ui );
  };


  /**
  * Control the state of a list.
  * It can be called from _keyHandler, _mouseHandler or API
  * and does list's changes depending from reseived params.
  * @method _controller
  * @private
  * @param {Object|null} event Event object
  * @param {Object} params Current params.
  **/
  Plugin.prototype._controller = function( e, params ) {
    var method;
    params.changedItems = [];
    params.prevItemsStates = [];
    delete this._isPrevented;
    this._trigger('before', e, params);

    if( this._isPrevented ) {
      this._cancel( e, params );
      this._stop( e, params );
      return;
    }
    params.wasSelected = ( this._selected > 0 );
    if ( params.target && params.isTargetWasSelected === undefined ) {
      params.isTargetWasSelected = this._getIsSelected( params.target );
    }
    
    if (
      params.isRangeSelect && 
      params.isTargetWasSelected && 
      params.target === this.ui.focus
    ) {
      // do nothing

    // Range
    } else if ( params.isRangeSelect ) {
      this._perfomRangeSelect( e, params);

    // Multi
    } else if ( params.isMultiSelect ) {
      method = params.isTargetWasSelected ? this._unselect : this._select;
      method.call( this, e, params, params.items );

    // Moving focus be mouse
    } else if ( params.target && !params.items && e.type === 'mouseover' ) {
      // do nothing - focus will be set

    // Single selection
    } else if ( params.target && params.items ) {

      // If there is one selected item and it is focused
      if ( this._selected && this._selected === 1 && this._getIsSelected(this.ui.focus) ) {
        /* It is case, when user moves cursor by keys or chooses single items by mouse 
        - need just clear selection from focus - no need run go whole DOM of list */
        this._unselect( e, params, this.ui.focus, params.isTargetWasSelected );

      } else if (this._selected) {
        this._unselectAll( e, params );
      }
      // Select item. Callback 'select' calls only if target was selected
      this._select( e, params, params.items, params.isTargetWasSelected );

    } else if ( !params.target && this._selected > 0 && this.options.get('selectionBlur') ) { 
        // Get exception - This option has been added by tmhao2005 using for HoGo pro
        var selector = this.options.get('exceptionBlur'), array = selector.split(','), check;
        for (var i in array) {
            if ( $(e.target).parents(array[i]).length > 0 )
                check = true;
        }
        if (!this._checkInExceptionBlur(e, params))
            this._unselectAll( e, params );
    }

    if( !this._selected && params.wasSelected ) {
      this._trigger('unselectAll', e, params);
    }
    
    params.prevFocus = ( this.ui.focus ) ? this.ui.focus : null;

    if ( !params.target && this.options.get('focusBlur') ) {
        if (!this._checkInExceptionBlur(e, params))
            this._blur(e, params);
    } else if ( params.target && !params.wasCancelled ) {
      this._setFocus( params.target );
    }
    
    // End of the cycle
    this._stop( e, params );
  };
  
  //
  // This function has been added by tmhao2005 to check
  //
  Plugin.prototype._checkInExceptionBlur = function(e, params) {
        if (e) {
            var $target = $(e.target), selector = this.options.get('exceptionBlur'), array = selector.split(','), check = false;
            for (var i in array) {
                if ($target.parents($.trim(array[i])).length > 0 || $target.hasClass($.trim(array[i]).substring(1))) {
                    check = true;
                    break;
                }
            }
            return check;
        }
        else 
            return false;
  };

  /**
  * Used by _controller to perform range selection in the list.
  * @method _perfomRangeSelect
  * @private
  * @param {Object|null} event Event object
  * @param {Object} params Current params.
  **/
  Plugin.prototype._perfomRangeSelect = function( e, params ) {
    var method, items, initial, beforeStart, afterStart, beforeEnd, afterEnd,

    endAfterStart = params.rangeStart < params.rangeEnd,
    allItems      = this._getItems( params ),
    top           = ( endAfterStart ) ? params.rangeStart : params.rangeEnd,
    bot           = ( endAfterStart ) ? params.rangeEnd : params.rangeStart;

    // New solid selectioin
    if ( params.isNewSolidSelection ) {
      // Get items from top to first range item (not include)
      items = allItems.slice( 0, top );
      // Add items from last range item (not include) to the end of list
      items = items.add( allItems.slice( bot + 1 ) );
      this._unselect( e, params, items );
      this._select( e, params, params.items );
    
    // Existing Solid selection and target is not selected
    // and initial selection's elem is in current range
    } else if (
      this.ui.solidInitialElem &&
      !params.isTargetWasSelected &&
      (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;
      beforeStart = initial < params.rangeStart;
      afterStart  = params.rangeStart < initial;
      beforeEnd   = initial < params.rangeEnd;
      afterEnd    = params.rangeEnd < initial;

      if (( !beforeEnd && beforeStart ) || ( !afterEnd && afterStart)) {
        // Items from range start to initial solid selection elem (but not include)
        items = afterStart ? allItems.slice( top, initial ) : allItems.slice( initial+1, bot+1 );
        if (items.length > 0) {
          this._unselect( e, params, items );
        }
      }
      if (( afterEnd && !afterStart ) || ( beforeEnd && !beforeStart )) {
        // Items from range end to initial selection elem (but not include)
        items = afterEnd ? allItems.slice( top, initial ) : allItems.slice( initial+1, bot+1 );
        if (items.length > 0) {
          this._select( e, params, items );
        }
      }

    // Common range select
    } else {
      method = params.isTargetWasSelected ? this._unselect : this._select;
      method.call( this, e, params, params.items );
    }
  };


  /**
  * Changes items states from unselected to selected and back.
  * Used by _select and _unselect methods.
  * @method _changeItemsStates
  * @private
  * @param {jQuery collection} items jQuery collection of items that will be change.
  * @param {Nubmer} delta Ss number to modifying selection counter
  *   above zero `delta` from _select/ sub zero `delta` from _unselect
  * @param {Object} params Current params.
  **/
  Plugin.prototype._changeItemsStates = function( items, delta, params ) {
    var
      aboveZero = delta > 0,
      changedItems = [],
      _this = this;
    
    $( items ).each( function( index, item ) {
      var
        isSelected = _this._getIsSelected( item ),
        // Condition - if item is not selected (_select) or items is selected (_unselect)
        selectedCondition = ( aboveZero ) ? !isSelected : isSelected,
        isSelectedTarget = ( item === params.target && params.isTargetWasSelected );

      /*  If it's unselecting and item is selected target,
        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( selectedCondition ) {
        if( !params.isCancellation ) {
          changedItems.push( item );
          params.prevItemsStates.push( isSelected );
        }
        _this._selected += delta;
      }
      $( item ).toggleClass( _this.options.get('selectedClass'), aboveZero );

    });

    if( !params.isCancellation ) {
      params[ (aboveZero?'selected':'unselected') ] = $( changedItems );
      params.changedItems = params.changedItems.concat( changedItems );
    }
  };


  /**
  * Select items.
  * @method _select
  * @private
  * @param {Object} event Event object.
  * @param {Object} params Current params.
  * @param {jQuery collection} items jQuery collection of items that will be selected.
  * @param {Boolean} [silent] If true then `select` callback won't be called.
  **/
  Plugin.prototype._select = function( e, params, items, silent ) {
    this._changeItemsStates( items, 1, params);
    if ( !silent ) { this._trigger('select', e, params); }
    if( this._isPrevented && !params.isCancellation ) { this._cancel( e, params ); }
  };


  /**
  * Unselect items.
  * @method _unselect
  * @private
  * @param {Object} event Event object.
  * @param {Object} params Current params.
  * @param {jQuery collection} items jQuery collection of items that will be unselected.
  * @param {Boolean} [silent] If true then `unselect` callback won't be called.
  **/
  Plugin.prototype._unselect = function( e, params, items, silent ) {
    this._changeItemsStates( items, -1, params );
    if ( !silent ) { this._trigger('unselect', e, params); }
    if( this._isPrevented && !params.isCancellation ) { this._cancel( e, params ); }
  };


  /**
  * Unselect all items.
  * @method _unselectAll
  * @private
  * @param {Object} event Event object.
  * @param {Object} params Current params.
  **/
  Plugin.prototype._unselectAll = function( e, params ) {
    var isOnlyTargetSelected, items;
    if( !this._selected || this._selected === 0 ) { return; }

    items = this._getItems( params );
    // target was only selected item ( flag used for preventing callback )
    isOnlyTargetSelected = params.target && params.isTargetWasSelected && this._selected === 1;
    this._unselect( e, params, items, isOnlyTargetSelected );
  };


  /**
  * Turn on multiselect mode.
  * @method _multiSelect
  * @private
  * @param {Object} params Current params.
  **/
  Plugin.prototype._multiSelect = function( params ) {
    params.isMultiSelect = true;
    return $( params.target );
  };


  /**
  * Turn on range select mode.
  * @method _rangeSelect
  * @private
  * @param {Object} params Current params.
  * @return {jQuery object} Range of items between focused and targeted elements (included).
  **/
  Plugin.prototype._rangeSelect = function( params ) {
    params.isRangeSelect = true;
    if( params.target === this.ui.focus ) { return $( params.target ); }

    // Detect position of target and focus in the list
    var arr = params.allItems ? params.allItems : this._getItems( params ),
      x = arr.index( params.target ),
      y = arr.index( this.ui.focus ),

    // Get array of items between focus and target
    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;
  };


  /**
  * Check if element is selected.
  * @method _getIsSelected
  * @private
  * @param {HTMLElement} target
  * @return {Boolean} true if element is selected.
  **/
  Plugin.prototype._getIsSelected = function( target ) {
    var options = this.options.get();

    if( $(target).length <= 1 ) {
      return $( target ).hasClass( options.selectedClass );
    }
    return $.map( $(target), function( item ) {
      return $( item ).hasClass( options.selectedClass );
    });
  };


  /**
  * Clears focus of the list.
  * @method _blur
  * @private
  * @param {Object} event Event object.
  * @param {Object} params Current params.
  * @param {Boolean} [silent] If true `focusLost` callback won't be called.
  **/
  Plugin.prototype._blur = function( e, params, silent ) {
    if( !silent && this.ui.focus ) {
      this._trigger('focusLost', e, params);
    }
    if( this.ui.focus ) {
      $( this.ui.focus ).removeClass( this.options.get('focusClass') );
      delete this.ui.focus;
    }
  };


  /**
  * Set focus of the list.
  * @method _setFocus
  * @private
  * @param {HTMLElement} target
  **/
  Plugin.prototype._setFocus = function( target ) {
    if( !target ) { return; }
    if( this.ui.focus ) {
      $(this.ui.focus).removeClass( this.options.get('focusClass') );
    }
    this.ui.focus = target;
    $( this.ui.focus ).addClass( this.options.get('focusClass') );
    return this.ui.focus;
  };


  /**
  * Finalization function of every cycle.
  * @method _stop
  * @private
  * @param {Object} event Event object.
  * @param {Object} params Current params.
  **/
  Plugin.prototype._stop = function( e, params ) {
    this._trigger('stop', e, params);
    if( this._isPrevented ) { this._cancel( e, params ); }
  };


  /**
  * Checks if object is HTMLElement or element wrapped in jQuery
  * @method _checkIfElem
  * @private
  * @param {any} selector
  * @return {Boolean} True if `selector` is element of jQuery element.
  **/
  Plugin.prototype._checkIfElem = function( selector ) {
    var res;
    if ( selector && (selector.jquery || selector.zepto || selector.nodeType) ) {
      selector = (selector.jquery||selector.zepto) ? selector : $( selector );
      res = selector.filter( this._itemsSelector );
      return res.length > 0 ? res : null;
    } else { return false; }
  };


  /**
  * Checks if argument is a string and selector.
  * @method _checkIfSelector
  * @private
  * @param {any} selector
  * @return {jQuery object|false|null} If `selector` is a selector 
  *   and match some elements then method return these elements.
  **/
  Plugin.prototype._checkIfSelector = function( selector ) {
    var res;
    if ( selector && typeof selector === 'string') {
      res = this.$el
        .find( selector )
        .filter( this._itemsSelector );
      return ( res.jquery && res.length > 0 ) ? res : null;
    
    } else { return false; }
  };

  

  /* ==============================================================================

  Keyboard

  */
  /**
  * Handles keyboard events and calls _controller.
  * @method _keyHandler
  * @private
  * @param {Object} event
  **/
  Plugin.prototype._keyHandler = function( e ) {

    if ( !this.options.get('keyboard') ) { return; }
    if ( this.options.get('preventInputs') && e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') { return; }
    var key = e.which, params = {}, target, isAllSelect, direction, page;

    if (e.type === 'keyup') {
      if ( key === Plugin.keyCode.SHIFT ) {
        delete this._shiftModeAction; // while SHIFT is held
        delete this._keyModes.shift; // arrow key (UP,DOWN) which pressed first in SHIFT mode
      }
      return;
    }
    if ( key === Plugin.keyCode.A && this._isMulti(e) && this.options.get('multi') ) {
      target = this._getItems( params );
      isAllSelect = true; // flag that is all items is selected

    } else {
      // Choose direction and try to find targeted item
      switch ( key ) {
      case Plugin.keyCode.DOWN:
        direction = 'next';
        target    = this._findNextTarget( 'next', params );
        break;
      case Plugin.keyCode.UP:
        direction = 'prev';
        target    = this._findNextTarget( 'prev', params );
        break;
      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.PAGE_DOWN:
      case Plugin.keyCode.PAGE_UP:
        var isDown = key === Plugin.keyCode.PAGE_DOWN;
        direction  = isDown ? 'next' : 'prev';
        page       = isDown ? 'pagedown' : 'pageup';
        params.isShiftPageRange = this.options.get('multi') && e.shiftKey && !isAllSelect;
        target = this._findNextTarget( page, params );
        break;
      case Plugin.keyCode.SPACE:
        target = $( this.ui.focus );
        break;
      case Plugin.keyCode.ENTER:
        if ( !this.options.get('multi') ) { target = $( this.ui.focus ); }
        break;
      }
    }
    // One of the arrows was pressed
    if ( target && target.length > 0 ) {
      e.preventDefault(); // Disable default window scroll by arrow keys

      params.target = target[0];
      params.items = target;

      // Toggle mode
      if ( this.options.get('keyboardMode') === 'toggle' ) {
        if (
          key !== Plugin.keyCode.SPACE &&
          !(key === Plugin.keyCode.ENTER && !this.options.get('multi'))
        ) {
          delete params.items;
        }
        if ( this.options.get('multi') ) { params.isMultiSelect = true; }
        delete this.ui.solidInitialElem;

      // SHIFT mode
      } else if ( this.ui.focus && this.options.get('multi') && e.shiftKey && !isAllSelect ) {
        // Call multiVariator or rangeVariator - 
        // it set all needed params depends from arguments
        if (
          key === Plugin.keyCode.END     || key === Plugin.keyCode.HOME ||
          key === Plugin.keyCode.PAGE_UP || key === Plugin.keyCode.PAGE_DOWN
        ) {
          this._rangeVariator( params );
        } else {
          this._multiVariator( params, key, direction, target );
        }

        // Set solid selection
        if ( !this.ui.solidInitialElem && params.target !== this.ui.focus ) {
          this.ui.solidInitialElem = this.ui.focus;
          params.isNewSolidSelection = true;
        }

        // Set selection mode
        if ( !this._shiftModeAction ) { this._shiftModeAction = 'select'; }
        if ( !this._keyModes.shift  ) { this._keyModes.shift  = key;      }

      } else {
        delete this.ui.solidInitialElem;
      }
      this._controller( e, params );
      this.scroll();
    } else {
      params.prevItemsStates = [];
      this._trigger('before', e, params);
      this._trigger('stop', e, params);
    }
  };


  /**
  * Sets range or multi modes for selection depending from `params`.
  * @method _rangeVariator
  * @private
  * @param {Object} params Current params.
  **/
  Plugin.prototype._rangeVariator = function( params ) {
    var
      isFocusSelected = void 0 === params.isFocusSelected ? this._getIsSelected( this.ui.focus ) : params.isFocusSelected,
      isTargetSelected = params.isTargetWasSelected = this._getIsSelected( params.target );

    if ( !isFocusSelected && !isTargetSelected ) {
      // Only target will be selected
      params.target = params.items = this.ui.focus;
      params.isMultiSelect = true;
    } else {
      params.items = this._rangeSelect( params );
      // Cut target from end or begin because we do not want to unselect it
      if ( isTargetSelected ) {
        params.items = params.rangeStart < params.rangeEnd ? (
        params.items.slice(0, params.items.length-1)
        ) : (params.items.slice(1) );
      }
    }
  };


  /**
  * FOR SHIFT MODE ONLY
  * - turns on shift mode flags
  * - solves different situations with shift+arrows selection
  * @method _multiVariator
  * @private
  * @param {Object} params Current params.
  * @param {Number} key Keycode of pressed key.
  * @param {String} direction Indicates 'prev' or 'next' item to find.
  * @param {HTMLElement} target Targeted element.
  **/
  Plugin.prototype._multiVariator = function( params, key, direction, target ) {
    var
      isFocusSelected       = void 0 === params.isFocusSelected ? this._getIsSelected( this.ui.focus ) : params.isFocusSelected,
      isTargetSelected      = this._getIsSelected( params.target ),
      afterTarget           = this._getItems( params, direction, target ), // Search for next target in the same direction
      isSelectedAfterTarget = this._getIsSelected( afterTarget ), // Check if second target is selected
      prevItem;

    // If another arrow was pressed that means the direction was changed
    if ( this._keyModes.shift && this._keyModes.shift !== key ) {
      this._keyModes.shift = this._shiftModeAction = null;
    }

    if ( this._keyModes.shift && this._shiftModeAction === 'select' && isTargetSelected ) {
      /* When user select range of items by holding SHIFT and presses arrow key, there are already can be
      selected items - focus should jump through these selected items to first unselected item */

      // While first unselected item will be found or edge of the list will be reached
      while( this._getIsSelected(params.items) && params.items.length > 0 ) {
        prevItem = params.items;
        params.items = this._getItems( params, direction, params.items );
      }
      // If unselected item was found it becomes target item
      // target will be selected and get the focus
      params.target = params.items ? params.items : prevItem;

    } else if ( isTargetSelected && isFocusSelected && !isSelectedAfterTarget ) {
      /* Sitiation is possible when user unselect items by arrow key with holding SHIFT */

      // Clear flags of serial selection by SHIFT
      this._keyModes.shift = this._shiftModeAction = null;
      params.items = this.ui.focus;
      // Selection will be clear on the focus, focus will be set on target item

    } else if ( isFocusSelected && isTargetSelected ) {
      params.items = this.ui.focus;
      // If there is no SHIFT action (first pressing arrow key with holding SHIFT)
      // Set mode of selection
      if ( !this._shiftModeAction ) { this._shiftModeAction = 'unselect'; }
      // Selection will be clear on the focus, focus will be set on target item

    } else if ( !isFocusSelected ) {
      // Focus will be selected
      params.target = params.items = this.ui.focus;
    }
    params.isMultiSelect = true;
  };


  /**
  * Used by _keyHandler
  * when UP, DOWN, PageUp, PageDown keys has pressed - find target or first/last element of the list
  * @method _findNextTarget
  * @private
  * @param {String} direction Indicates 'prev' 'next' or 'pageup' 'pagedown' item to find 
  *   relative to the focused element.
  * @param {Object} params Current params.
  * @return {jQuery object|null} Targeted element.
  **/
  Plugin.prototype._findNextTarget = function( direction, params ) {
    var edge = ( direction === 'next' || direction === "pagedown" ) ? 'first' : 'last', // extreme item of the list
      // If there is the focus - try to find next sibling
      // else get first|last item of the list - depends from direction
      res = ( this.ui.focus ) ? this._getItems( params, direction, this.ui.focus ) : this._getItems( params, edge );

    // If has not found any items and loop option is ON
    if ( (res === null || res.length === 0) && this.options.get('loop') ) {
      res = this._getItems( params, edge ); // find extreme item
    }
    return res;
  };


  /**
  * Used by _keyHandler or public scroll method.
  * Recalculates scroll position, if focused item is not visible in container's viewport.
  * @method _refreshBoxScroll
  * @private
  * @param {HTMLElement} box Container whose scroll will be calculated for showing focused element.
  **/
  Plugin.prototype._refreshBoxScroll = function( box ) {
    var
      $box          = $( box ),
      isWindow      = box === window,
      boxViewHeight = isWindow ? $box[outerHeight]() : box.clientHeight,
      boxScrollTop  = $box.scrollTop(),
      boxWindowY    = isWindow ? 0 : $box.offset().top,
      $item         = $( this.ui.focus ),
      itemHeight    = $item[outerHeight](),
      itemBoxTop    = isWindow ? $item.offset().top : ( $item.offset().top - boxWindowY + boxScrollTop );

    if ( itemBoxTop < boxScrollTop ) {
      $box.scrollTop( itemBoxTop );
    
    } else if ( (itemBoxTop + itemHeight) > (boxScrollTop + boxViewHeight) ) {
      // Scroll to bottom edge of elem - 
      // bottom edges of item and viewport will be on the same Y
      $box.scrollTop( itemBoxTop + itemHeight - boxViewHeight );
    }
  };


  /**
  * @method _isRange
  * @private
  * @param {Object} event Event object.
  * @return {Boolean} True if it's range select with shift.
  **/
  Plugin.prototype._isRange = function( e ) {
    return e.shiftKey || (e.shiftKey && e.ctrlKey) || (e.shiftKey && e.metaKey);
  };


  /**
  * @method _isMulti
  * @private
  * @param {Object} event Event object.
  * @return {Boolean} True if it's multi-select with ctrl.
  **/
  Plugin.prototype._isMulti = function( e ) {
    return e.ctrlKey || e.metaKey;
  };



  /* ==============================================================================

  Mouse

  */
  /**
  * Mouse events handler - set necessary paramaters and calls _controller
  * @method _mouseHandler
  * @private
  * @param {Object} event Event object.
  **/
  Plugin.prototype._mouseHandler = function( e ) {
    var
    options = this.options.get(),
    type    = e.type,
    isMulti = this._isMulti(e),
    isRange = this._isRange(e),
    params  = {};

    /* Find target: */
    if (options.mouseMode === 'mouseup') {
      params.target = this._getTarget(e);
      if ( type === 'click' || (params.target && type === 'mousedown')) { return; }

    // because this click may be after mousedown in multi/range mode
    } else if (type === 'click' && !this._mousedownOnItem) {
      return;

    } else if (type === 'mousedown' || type === 'click') {
      params.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' && params.target && !( options.multi && (isMulti||isRange) && options.mouseMode === 'standard' )) {
        this._mousedownOnItem = params.target;
        return;
      }
      delete this._mousedownOnItem;
      // to prevent blurring when mousedown on element and mouseup on another element
      if (!params.target && type === 'click') {return;}
    } else { return; }

    if( options.multi && params.target ) {

      // Range select
      if ( isRange && this.ui.focus ) {
        params.items = this._rangeSelect( params );

      // Add/subtract to selection
      } else if ( isMulti || options.mouseMode === 'toggle' ) {
        params.items = this._multiSelect( params );
      }
    }

    if ( params.target && !params.items ) { params.items = $( params.target ); }
    delete this.ui.solidInitialElem;
    this._controller( e, params );
  };


  /**
  * Tries to find target under cursor when mouse moves
  * @method _mousemoveHandler
  * @private
  * @param {Object} event Event object.
  **/
  Plugin.prototype._mousemoveHandler = function( e ) {
    if ( this._isFocusOnHoverPrevented ) { return; }
    var params = {}, target = this._getTarget(e);

    if ( target ) {
      delete this.ui.solidInitialElem;
      this._isHovered = true;
      if ( target !== this.ui.focus ) {
        params.target = target;
        this._controller( e, params );
      }
    } else if ( this._isHovered ) {
      this._isHovered = false;
      this._controller( e, params );
    }
  };


  /**
  * Prevent changing focus under cursor when user moves focus by keyboard
  * and list's element changes scroll position
  * @method _preventMouseMove
  * @private
  **/
  Plugin.prototype._preventMouseMove = function() {
    var _this = this;
    this._isFocusOnHoverPrevented = true;
    
    if ( this._focusHoverTimeout ) {
      clearTimeout( this._focusHoverTimeout );
      delete this._focusHoverTimeout;
    }
    this._focusHoverTimeout = setTimeout( function() {
      delete _this._isFocusOnHoverPrevented;
      delete _this._focusHoverTimeout;
    }, 250);
  };



  /* ==============================================================================

  Public API

  */
  /**
  * Searches public method and calls it if exists.
  * @method _callPublicMethod
  * @private
  * @param {String} method
  **/
  Plugin._callPublicMethod = function( method ) {
    var
      _this = Plugin.getDataObject( this ),
      publicMethod, args;

    if( null === _this || void 0 === _this ) {
      throw new Error( 'Element ' + this[0] + ' has no plugin ' + Plugin.pluginName );
    }
    // Try to find method
    if ( _this[method] && $.isFunction(_this[method]) ) {
      publicMethod = _this[method];
    }
    // If method exists and it is not private - call him
    if ( publicMethod && $.isFunction( publicMethod ) && method.charAt(0) !== '_' ) {
      args = Array.prototype.slice.call( arguments );
      args.shift();
      return publicMethod.apply( _this, args );
    }
    throw new Error( 'Plugin \"' + Plugin.pluginName + '\" has no method \"' + method + '\"' );
  };


  /**
  * @method isEnabled
  * @return {Boolean} True if selectable list is enabled.
  **/
  Plugin.prototype.isEnabled = function() {
    return this._isEnable;
  };


  /**
  * Sets plugin's instance options
  * @method option
  * @param {Object|String} option Hash of options or option name.
  * @param {any} [value] Value of an option if first argument is a string.
  * @return Option value if option name was passed as one argument.
  **/
  Plugin.prototype.option = function( option, value ) {
    var args = arguments.length;

    // Received string
    if( args > 0 && typeof option === 'string' ) {
      // Received strings and any argument
      if( args > 1 ) {
        var opt = {};
        opt[option] = value;
        this.options.set( opt );
        return this.$el;
      }
      // Return value of option
      return this.options.get( option );
    }
    // Received object
    if( args > 0 && $.isPlainObject( option ) ) {
      this.options.set( option );
      return this.$el;
    }
    // Return whole options object
    if ( args === 0 ) {
      return this.options.get();
    } else {
      throw new Error('Format of \"option\" could be: \"option\" or \"option\",\"name\" or \"option\",\"name\",val or \"option\",{}');
    }
  };


  /**
  * Destroy plugin's instance. Detaches event handlers and removes HTML-classes.
  * @method destroy
  **/
  Plugin.prototype.destroy = function() {
    this._trigger('destroy');
    this._unbindEvents();
    if ( this._focusHoverTimeout ) { clearTimeout(this._focusHoverTimeout); }
    if( this.ui.focus ) {
      $(this.ui.focus).removeClass( this.options.get('focusClass') );
      delete this.ui.focus;
    }
    if( this._selected > 0 ) {
      this.getSelected().removeClass( this.options.get('selectedClass') );
    }
    this.$el.removeClass( this.options.get('disabledClass') );
    this.$el.removeClass( this.options.get('listClass') );
    this.options.off();
    delete this.options;
    delete this._scrolledElem;
    delete this.ui.solidInitialElem;
    this.$el.removeData( 'plugin_' + Plugin.pluginName );
    this.$el = null;
    return;
  };


  /**
  * Uunselect one or more items in the list. It is multi-selection.
  * @method unselect
  * @param {HTMLElement|String} selector A selector or element or set of elements to select.
  **/
  Plugin.prototype.unselect = function( selector ) {
    return this.select( selector, true );
  };


  /**
  * Select/unselect one or more items in the list. It is multi-selection.
  * @method select
  * @param {HTMLElement|String} selector A selector or element or set of elements to select.
  * @param {Boolean} revert If true then method will work as `unselect`.
  **/
  Plugin.prototype.select = function( selector, revert ) {
    var $elem, params;

    if ( revert === true && selector === void 0 ) {
      // To unselecting all items
      params = {
        isTargetWasSelected: true,
        isMultiSelect: true
      };
      params.items = this._getItems( params );
    
    } 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.'); }
      params = {
        items: ( $elem.addClass ) ? $elem : $( $elem ),
        isTargetWasSelected: (revert) ? true : false,
        isMultiSelect: true
      };
    }

    delete this.ui.solidInitialElem;
    this._controller( null, params);
    return this.$el;
  };


  /**
  * Clear focus and selection of list's options allow that.
  * @method blur
  **/
  Plugin.prototype.blur = function() {
    this._controller( null, { target: null } );
    return this.$el;
  };


  /**
  * Get selected items in the list.
  * @method getSelected
  * @param {Boolean} [getIds] If true method return array of IDs of selcted elements.
  * @return {Array|jQuery object} Selected items as jQuery collection or array of IDs.
  **/
  Plugin.prototype.getSelected = function( getIds ) {
    var arr,
    items = this._getItems({}).filter( '.' + this.options.get('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;
  };


  /**
  * Get array of IDs of  selected items in the list.
  * @method getSelectedId
  * @return {Array} Selected items as array of IDs.
  **/
  Plugin.prototype.getSelectedId = function() {
    return this.getSelected( true );
  };


  /**
  * Focus one element.
  * @method focus
  * @param {HTMLElement|String} selector A selector or element to select.
  **/
  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 || $elem.zepto) ) {
        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; }
  };


  /**
  * Recalculate scroll of scrollable element and window.
  * @method scroll
  **/
  Plugin.prototype.scroll = function() {
    this._preventMouseMove();
    if (this.ui.focus) {
      if ( this._scrolledElem ) { this._refreshBoxScroll( this._scrolledElem ); }
      this._refreshBoxScroll( window );
    }
  };


  /**
  * Enable disabled selectable list.
  * @method enable
  **/
  Plugin.prototype.enable = function() {
    this._isEnable = true;
    this.$el.removeClass( this.options.get('disabledClass') );
    return this.$el;
  };


  /**
  * Disable selectable list.
  * @method disable
  **/
  Plugin.prototype.disable = function() {
    this._isEnable = false;
    this._isHovered = false;
    this.$el.addClass( this.options.get('disabledClass') );
    return this.$el;
  };


  /**
  * Cancel changes in current cycle. This method
  * only could be invoked from callbacks function.
  * If called from `before` callback, then it will premvent any changes.
  * @method cancel
  **/
  Plugin.prototype.cancel = function() {
    this._isPrevented = true;
    return this.$el;
  };

  /**
  * Refresh plugins data. Sould be used when some selected
  * items was deleted from the list.
  * @method refresh
  **/
  Plugin.prototype.refresh = function() {
    var focus = this.ui.focus;
    if ( focus && !$(focus).is(':visible') ) { delete this.ui.focus; }
    this._selected = ( this.getSelected() ).length;
    return this.$el;
  };



  /* ==============================================================================

  Method of jQuery.fn

  */
  $.fn[Plugin.pluginName] = function( options ) {
    if( options && options.charAt ) {
      return Plugin._callPublicMethod.apply( this, arguments );
    }
    return this.each( function(key, elem) {
      if ( !Plugin.getDataObject(elem) ) { new Plugin( elem, options ); }
    });
  };

  $.fn[Plugin.pluginName].defaults = defaults;





  /* DEVELOPMENT */
  /* DO NOT CHANGE THIS!
  All code below DEVELOPMENT line ( except closing function wrapper )
  will be automatically removed for production by grunt replace task */
  window[ '_' + Plugin.pluginName ] = Plugin;





}((window.jQuery || window.Zepto), window));