diff options
author | Giovanni Giacobbi <giovanni@giacobbi.net> | 2011-10-18 16:57:41 +0200 |
---|---|---|
committer | Giovanni Giacobbi <giovanni@giacobbi.net> | 2011-10-18 16:57:41 +0200 |
commit | bd1865efd23832ffbc9371169e0d53bd3ad445b0 (patch) | |
tree | 12e52900aa11bfc4a13b43c5b9db841218301074 | |
parent | 410b0b6cd0f14b261d236abde2b79f2c16cc3cc2 (diff) | |
download | jquery-ui-bd1865efd23832ffbc9371169e0d53bd3ad445b0.tar.gz jquery-ui-bd1865efd23832ffbc9371169e0d53bd3ad445b0.zip |
New sequential typeahead implementation which supports both prefix matching and first letter cycling. The no longer useful 'typeAhead' option now became the default delay given for the type ahead
-rw-r--r-- | ui/jquery.ui.selectmenu.js | 129 |
1 files changed, 76 insertions, 53 deletions
diff --git a/ui/jquery.ui.selectmenu.js b/ui/jquery.ui.selectmenu.js index fa7e21aaf..38e42c7dc 100644 --- a/ui/jquery.ui.selectmenu.js +++ b/ui/jquery.ui.selectmenu.js @@ -17,7 +17,7 @@ $.widget("ui.selectmenu", { eventPrefix: "selectmenu", options: { transferClasses: true, - typeAhead: "sequential", + typeAhead: 1000, style: 'dropdown', positionOptions: { my: "left top", @@ -130,13 +130,18 @@ $.widget("ui.selectmenu", { case $.ui.keyCode.TAB: ret = true; break; + case $.ui.keyCode.HOME: + self.index(0); + break; default: ret = true; } return ret; }) .bind('keypress.selectmenu', function(event) { - self._typeAhead(event.which, 'mouseup'); + if (event.which > 0) { + self._typeAhead(event.which, 'mouseup'); + } return true; }) .bind('mouseover.selectmenu focus.selectmenu', function() { @@ -247,7 +252,9 @@ $.widget("ui.selectmenu", { return ret; }) .bind('keypress.selectmenu', function(event) { - self._typeAhead(event.which, 'focus'); + if (event.which > 0) { + self._typeAhead(event.which, 'focus'); + } return true; }) // this allows for using the scrollbar in an overflowed list @@ -459,63 +466,79 @@ $.widget("ui.selectmenu", { $.Widget.prototype.destroy.apply(this, arguments); }, - _typeAhead: function( code, eventType ){ - var self = this, focusFound = false, C = String.fromCharCode(code).toUpperCase(); - c = C.toLowerCase(); + _typeAhead: function( code, eventType ) { + var self = this, + c = String.fromCharCode(code).toLowerCase(), + items = this.list.find( 'li a' ), + matchee = null, + nextIndex = null; + + // Clear any previous timer if present + if ( self._typeAhead_timer ) { + window.clearTimeout( self._typeAhead_timer ); + self._typeAhead_timer = undefined; + } - if ( self.options.typeAhead == 'sequential' ) { - // clear the timeout so we can use _prevChar - window.clearTimeout('ui.selectmenu-' + self.selectmenuId); + // Store the character typed + self._typeAhead_chars = (self._typeAhead_chars === undefined ? "" : self._typeAhead_chars).concat(c); - // define our find var - var find = typeof( self._prevChar ) == 'undefined' ? '' : self._prevChar.join( '' ); + // Detect if we are in cyciling mode or direct selection mode + if ( self._typeAhead_chars.length < 2 || + (self._typeAhead_chars.substr(-2, 1) === c && self._typeAhead_cycling) ) { + self._typeAhead_cycling = true; - function focusOptSeq( elem, ind, c ){ - focusFound = true; - $( elem ).trigger( eventType ); - typeof( self._prevChar ) == 'undefined' ? self._prevChar = [ c ] : self._prevChar[ self._prevChar.length ] = c; - } - this.list.find( 'li a' ).each( function( i ) { - if ( !focusFound ) { - // allow the typeahead attribute on the option tag for a more specific lookup - var thisText = $( this ).attr( 'typeahead' ) || $(this).text(); - if ( thisText.indexOf( find + C ) === 0 ) { - focusOptSeq( this, i, C ); - } else if (thisText.indexOf(find+c) === 0 ) { - focusOptSeq( this, i, c ); - } - } - }); - // set a 1 second timeout for sequenctial typeahead - // keep this set even if we have no matches so it doesnt typeahead somewhere else - window.setTimeout( function( el ) { - self._prevChar = undefined; - }, 1000, self ); + // Match only the first character and loop + matchee = c; + } + else { + // We won't be cycling anymore until the timer expires + self._typeAhead_cycling = false; - } else { - // define self._prevChar if needed - if ( !self._prevChar ) { self._prevChar = [ '' , 0 ]; } - - focusFound = false; - function focusOpt( elem, ind ){ - focusFound = true; - $( elem ).trigger( eventType ); - self._prevChar[ 1 ] = ind; - } - this.list.find( 'li a' ).each(function( i ){ - if (!focusFound){ - var thisText = $(this).text(); - if ( thisText.indexOf( C ) === 0 || thisText.indexOf( c ) === 0 ) { - if (self._prevChar[0] == C){ - if ( self._prevChar[ 1 ] < i ){ focusOpt( this, i ); } - } else{ - focusOpt( this, i ); - } + // Match all the characters typed + matchee = self._typeAhead_chars; + } + + // We need to determine the currently active index, but it depends on + // the used context: if it's in the element, we want the actual + // selected index, if it's in the menu, just the focused one + // I copied this code from _moveSelection() and _moveFocus() + // respectively --thg2k + var selectedIndex = (eventType !== 'focus' ? + this._selectedOptionLi().data('index') : + this._focusedOptionLi().data('index')) || 0; + + for (var i = 0; i < items.length; i++) { + var thisText = items.eq(i).text().substr(0, matchee.length).toLowerCase(); + + if ( thisText === matchee ) { + + if ( self._typeAhead_cycling ) { + if ( nextIndex === null ) + nextIndex = i; + + if ( i > selectedIndex ) { + nextIndex = i; + break; } + } else { + nextIndex = i; } - }); - this._prevChar[ 0 ] = C; + } } + + if ( nextIndex !== null ) { + // Why using trigger() instead of a direct method to select the + // index? Because we don't what is the exact action to do, it + // depends if the user is typing on the element or on the popped + // up menu + items.eq(nextIndex).trigger( eventType ); + } + + self._typeAhead_timer = window.setTimeout(function() { + self._typeAhead_timer = undefined; + self._typeAhead_chars = undefined; + self._typeAhead_cycling = undefined; + }, self.options.typeAhead); }, // returns some usefull information, called by callbacks only |