11/*
2- JavaScript autoComplete v1.0.4
2+ JavaScript autoComplete v1.1.0
33 Copyright (c) 2014 Simon Steinberger / Pixabay
44 GitHub: https://github.com/Pixabay/JavaScript-autoComplete
55 License: http://www.opensource.org/licenses/mit-license.php
66*/
77
8+ // Chrome 38+, Edge 13+, Safari 8+, Firefox 26+, Opera 25+, all mobile browsers
9+
810var autoComplete = ( function ( ) {
911 // "use strict";
1012 function autoComplete ( options ) {
1113 if ( ! document . querySelector ) return ;
1214
1315 // helpers
14- function hasClass ( el , className ) { return el . classList ? el . classList . contains ( className ) : new RegExp ( '\\b' + className + '\\b' ) . test ( el . className ) ; }
15-
16- function addEvent ( el , type , handler ) {
17- if ( el . attachEvent ) el . attachEvent ( 'on' + type , handler ) ; else el . addEventListener ( type , handler ) ;
18- }
19- function removeEvent ( el , type , handler ) {
20- // if (el.removeEventListener) not working in IE11
21- if ( el . detachEvent ) el . detachEvent ( 'on' + type , handler ) ; else el . removeEventListener ( type , handler ) ;
22- }
2316 function live ( elClass , event , cb , context ) {
24- addEvent ( context || document , event , function ( e ) {
25- var found , el = e . target || e . srcElement ;
26- while ( el && ! ( found = hasClass ( el , elClass ) ) ) el = el . parentElement ;
27- if ( found ) cb . call ( el , e ) ;
28- } ) ;
17+ ( context || document ) . addEventListener ( event , function ( e ) {
18+ for ( var t = e . target || e . srcElement ; t ; t = t . parentElement )
19+ t . classList . contains ( elClass ) && ( cb . call ( t , e ) , t = 1 ) ;
20+ } , true ) ;
2921 }
3022
3123 var o = {
@@ -37,31 +29,55 @@ var autoComplete = (function(){
3729 offsetTop : 1 ,
3830 cache : 1 ,
3931 menuClass : '' ,
40- renderItem : function ( item , search ) {
32+ renderItem : function ( item , search ) {
4133 // escape special characters
42- search = search . replace ( / [ - \/ \\ ^ $ * + ? . ( ) | [ \] { } ] / g, '\\$&' ) ;
43- var re = new RegExp ( "(" + search . split ( ' ' ) . join ( '|' ) + ")" , "gi" ) ;
44- return '<div class="autocomplete-suggestion" data-val="' + item + '">' + item . replace ( re , "<b>$1</b>" ) + '</div>' ;
34+ var si = document . createElement ( 'div' ) ;
35+ si . className = 'autocomplete-suggestion' ;
36+ si . setAttribute ( 'data-val' , item ) ; // see PR#86
37+ try {
38+ search = search . replace ( / [ - \/ \\ ^ $ * + ? . ( ) | [ \] { } ] / g, '\\$&' ) ;
39+ si . innerHTML = item . replace ( new RegExp ( "(" + search . split ( ' ' ) . join ( '|' ) + ")" , "gi" ) , "<b>$1</b>" ) ;
40+ } catch ( e ) {
41+ si . textContent = item ;
42+ }
43+ return si ;
44+ } ,
45+ renderItems : function ( data , search , that ) {
46+ var tp = document . createElement ( 'template' ) ;
47+ var df = tp . content ;
48+ for ( var i = 0 ; i < data . length ; i ++ ) {
49+ var item = data [ i ] ;
50+ var si = o . renderItem ( item , search ) ;
51+ if ( typeof si === 'string' ) tp . innerHTML += si ;
52+ else if ( si && si . nodeType === 1 ) df . appendChild ( si ) ;
53+ }
54+ var _sc = that . sc ;
55+ var firstChild ;
56+ while ( firstChild = _sc . firstChild ) {
57+ firstChild . remove ( ) ;
58+ }
59+ _sc . appendChild ( df ) ;
4560 } ,
4661 onSelect : function ( e , term , item ) { }
4762 } ;
4863 for ( var k in options ) { if ( options . hasOwnProperty ( k ) ) o [ k ] = options [ k ] ; }
4964
5065 // init
5166 var elems = typeof o . selector == 'object' ? [ o . selector ] : document . querySelectorAll ( o . selector ) ;
52- for ( var i = 0 ; i < elems . length ; i ++ ) {
53- var that = elems [ i ] ;
67+ var forEach = function ( that ) {
5468
69+ that . _currentRequestId = 0 ;
5570 // create suggestions container "sc"
5671 that . sc = document . createElement ( 'div' ) ;
5772 that . sc . className = 'autocomplete-suggestions ' + o . menuClass ;
5873
5974 that . autocompleteAttr = that . getAttribute ( 'autocomplete' ) ;
6075 that . setAttribute ( 'autocomplete' , 'off' ) ;
61- that . cache = { } ;
76+ that . cache = new Map ( ) ; // changed from {} to Map; related to PR#37 PR#38
6277 that . last_val = '' ;
6378
64- that . updateSC = function ( resize , next ) {
79+ that . updateSC = function ( resize , next ) { // see issue mentioned in PR#49
80+ if ( that != document . activeElement ) return ; // issue#51 PR#52
6581 var rect = that . getBoundingClientRect ( ) ;
6682 that . sc . style . left = Math . round ( rect . left + ( window . pageXOffset || document . documentElement . scrollLeft ) + o . offsetLeft ) + 'px' ;
6783 that . sc . style . top = Math . round ( rect . bottom + ( window . pageYOffset || document . documentElement . scrollTop ) + o . offsetTop ) + 'px' ;
@@ -74,34 +90,34 @@ var autoComplete = (function(){
7490 if ( ! next ) that . sc . scrollTop = 0 ;
7591 else {
7692 var scrTop = that . sc . scrollTop , selTop = next . getBoundingClientRect ( ) . top - that . sc . getBoundingClientRect ( ) . top ;
77- if ( selTop + that . sc . suggestionHeight - that . sc . maxHeight > 0 )
93+ if ( selTop + that . sc . suggestionHeight - that . sc . maxHeight > 0 ) {
7894 that . sc . scrollTop = selTop + that . sc . suggestionHeight + scrTop - that . sc . maxHeight ;
79- else if ( selTop < 0 )
95+ } else if ( selTop < 0 ) {
8096 that . sc . scrollTop = selTop + scrTop ;
97+ }
8198 }
8299 }
83- }
84- addEvent ( window , 'resize' , that . updateSC ) ;
100+ } ;
101+ window . addEventListener ( 'resize' , that . updateSC ) ;
85102 document . body . appendChild ( that . sc ) ;
86103
87- live ( 'autocomplete-suggestion' , 'mouseleave' , function ( e ) {
104+ that . sc . addEventListener ( 'mouseleave' , function ( e ) {
88105 var sel = that . sc . querySelector ( '.autocomplete-suggestion.selected' ) ;
89- if ( sel ) setTimeout ( function ( ) { sel . className = sel . className . replace ( 'selected' , ' ') ; } , 20 ) ;
90- } , that . sc ) ;
106+ if ( sel ) setTimeout ( function ( ) { sel . classList . remove ( 'selected' ) ; } , 20 ) ;
107+ } ) ;
91108
92- live ( 'autocomplete-suggestion' , 'mouseover ' , function ( e ) {
109+ live ( 'autocomplete-suggestion' , 'mouseenter ' , function ( e ) {
93110 var sel = that . sc . querySelector ( '.autocomplete-suggestion.selected' ) ;
94- if ( sel ) sel . className = sel . className . replace ( 'selected' , ' ') ;
95- this . className += ' selected';
111+ if ( sel ) sel . classList . remove ( 'selected' ) ;
112+ this . classList . add ( ' selected') ;
96113 } , that . sc ) ;
97114
98115 live ( 'autocomplete-suggestion' , 'mousedown' , function ( e ) {
99- if ( hasClass ( this , 'autocomplete-suggestion' ) ) { // else outside click
100- var v = this . getAttribute ( 'data-val' ) ;
101- that . value = v ;
102- o . onSelect ( e , v , this ) ;
103- that . sc . style . display = 'none' ;
104- }
116+ e . stopPropagation ( ) ;
117+ var v = this . getAttribute ( 'data-val' ) ;
118+ that . value = v ;
119+ o . onSelect ( e , v , this ) ;
120+ that . sc . style . display = 'none' ;
105121 } , that . sc ) ;
106122
107123 that . blurHandler = function ( ) {
@@ -112,20 +128,24 @@ var autoComplete = (function(){
112128 setTimeout ( function ( ) { that . sc . style . display = 'none' ; } , 350 ) ; // hide suggestions on fast input
113129 } else if ( that !== document . activeElement ) setTimeout ( function ( ) { that . focus ( ) ; } , 20 ) ;
114130 } ;
115- addEvent ( that , 'blur' , that . blurHandler ) ;
116-
117- var suggest = function ( data ) {
118- var val = that . value ;
119- that . cache [ val ] = data ;
120- if ( data . length && val . length >= o . minChars ) {
121- var s = '' ;
122- for ( var i = 0 ; i < data . length ; i ++ ) s += o . renderItem ( data [ i ] , val ) ;
123- that . sc . innerHTML = s ;
131+ that . addEventListener ( 'blur' , that . blurHandler ) ;
132+
133+ var suggest = function ( data , val ) {
134+ val = val || that . value ; // PR#28
135+ that . cache . set ( val , data ) ;
136+ that . triggerSC ( data , val , val . length >= o . minChars ) ;
137+ } ;
138+
139+ // PR#40
140+ // Optional method to trigger results programatically
141+ that . triggerSC = function ( data , val , b ) {
142+ if ( data . length && b !== false ) {
143+ o . renderItems ( data , ( val || '' ) , that ) ;
124144 that . updateSC ( 0 ) ;
125- }
126- else
145+ } else {
127146 that . sc . style . display = 'none' ;
128- }
147+ }
148+ } ;
129149
130150 that . keydownHandler = function ( e ) {
131151 var key = window . event ? e . keyCode : e . which ;
@@ -134,16 +154,18 @@ var autoComplete = (function(){
134154 var next , sel = that . sc . querySelector ( '.autocomplete-suggestion.selected' ) ;
135155 if ( ! sel ) {
136156 next = ( key == 40 ) ? that . sc . querySelector ( '.autocomplete-suggestion' ) : that . sc . childNodes [ that . sc . childNodes . length - 1 ] ; // first : last
137- next . className += ' selected';
157+ next . classList . add ( ' selected') ;
138158 that . value = next . getAttribute ( 'data-val' ) ;
139159 } else {
140160 next = ( key == 40 ) ? sel . nextSibling : sel . previousSibling ;
161+ sel . classList . remove ( 'selected' ) ;
141162 if ( next ) {
142- sel . className = sel . className . replace ( 'selected' , '' ) ;
143- next . className += ' selected' ;
163+ next . classList . add ( 'selected' ) ;
144164 that . value = next . getAttribute ( 'data-val' ) ;
165+ } else {
166+ that . value = that . last_val ;
167+ next = 0 ;
145168 }
146- else { sel . className = sel . className . replace ( 'selected' , '' ) ; that . value = that . last_val ; next = 0 ; }
147169 }
148170 that . updateSC ( 0 , next ) ;
149171 return false ;
@@ -152,11 +174,19 @@ var autoComplete = (function(){
152174 else if ( key == 27 ) { that . value = that . last_val ; that . sc . style . display = 'none' ; }
153175 // enter
154176 else if ( key == 13 || key == 9 ) {
155- var sel = that . sc . querySelector ( '.autocomplete-suggestion.selected' ) ;
156- if ( sel && that . sc . style . display != 'none' ) { o . onSelect ( e , sel . getAttribute ( 'data-val' ) , sel ) ; setTimeout ( function ( ) { that . sc . style . display = 'none' ; } , 20 ) ; }
177+ var tsc = that . sc ;
178+ if ( tsc . style . display !== 'none' ) { // PR#8
179+ e . preventDefault ( ) ;
180+ }
181+ var sel = tsc . querySelector ( '.autocomplete-suggestion.selected' ) ;
182+ if ( sel && tsc . style . display != 'none' ) {
183+ o . onSelect ( e , sel . getAttribute ( 'data-val' ) , sel ) ;
184+ setTimeout ( function ( ) { tsc . style . display = 'none' ; } , 20 ) ;
185+ e . preventDefault ( ) ;
186+ }
157187 }
158188 } ;
159- addEvent ( that , 'keydown' , that . keydownHandler ) ;
189+ that . addEventListener ( 'keydown' , that . keydownHandler ) ;
160190
161191 that . keyupHandler = function ( e ) {
162192 var key = window . event ? e . keyCode : e . which ;
@@ -167,56 +197,68 @@ var autoComplete = (function(){
167197 that . last_val = val ;
168198 clearTimeout ( that . timer ) ;
169199 if ( o . cache ) {
170- if ( val in that . cache ) { suggest ( that . cache [ val ] ) ; return ; }
200+ var c = that . cache ;
201+ if ( c . has ( val ) ) { suggest ( c . get ( val ) ) ; return ; }
171202 // no requests if previous suggestions were empty
172- for ( var i = 1 ; i < val . length - o . minChars ; i ++ ) {
173- var part = val . slice ( 0 , val . length - i ) ;
174- if ( part in that . cache && ! that . cache [ part ] . length ) { suggest ( [ ] ) ; return ; }
203+ var k = o . minChars ;
204+ for ( var j = val . length - 1 ; j >= k ; j -- ) {
205+ var part = val . slice ( 0 , j ) ;
206+ if ( c . has ( part ) && ! c . get ( part ) . length ) { suggest ( [ ] ) ; return ; }
175207 }
176208 }
177- that . timer = setTimeout ( function ( ) { o . source ( val , suggest ) } , o . delay ) ;
209+ // PR#5
210+ that . timer = setTimeout ( function ( ) {
211+ var thisRequestId = ++ that . _currentRequestId ;
212+ o . source ( val , function ( data , val ) {
213+ if ( thisRequestId === that . _currentRequestId ) return suggest ( data , val ) ;
214+ } ) ;
215+ } , o . delay ) ;
178216 }
179217 } else {
180218 that . last_val = val ;
181219 that . sc . style . display = 'none' ;
182220 }
183221 }
184222 } ;
185- addEvent ( that , 'keyup' , that . keyupHandler ) ;
223+ that . addEventListener ( 'keyup' , that . keyupHandler ) ;
186224
187225 that . focusHandler = function ( e ) {
188226 that . last_val = '\n' ;
189227 that . keyupHandler ( e )
190228 } ;
191- if ( ! o . minChars ) addEvent ( that , 'focus' , that . focusHandler ) ;
229+ if ( ! o . minChars ) that . addEventListener ( 'focus' , that . focusHandler ) ;
230+ }
231+ for ( var i = 0 ; i < elems . length ; i ++ ) {
232+ forEach ( elems [ i ] ) ;
192233 }
193234
194235 // public destroy method
195236 this . destroy = function ( ) {
196- for ( var i = 0 ; i < elems . length ; i ++ ) {
197- var that = elems [ i ] ;
198- removeEvent ( window , 'resize' , that . updateSC ) ;
199- removeEvent ( that , 'blur' , that . blurHandler ) ;
200- removeEvent ( that , 'focus' , that . focusHandler ) ;
201- removeEvent ( that , 'keydown' , that . keydownHandler ) ;
202- removeEvent ( that , 'keyup' , that . keyupHandler ) ;
203- if ( that . autocompleteAttr )
204- that . setAttribute ( 'autocomplete' , that . autocompleteAttr ) ;
205- else
206- that . removeAttribute ( 'autocomplete' ) ;
207- document . body . removeChild ( that . sc ) ;
208- that = null ;
237+ var elems = this . elems ;
238+ if ( elems ) {
239+ this . elems = null ;
240+ for ( var i = 0 ; i < elems . length ; i ++ ) {
241+ var that = elems [ i ] ;
242+ window . removeEventListener ( 'resize' , that . updateSC ) ;
243+ that . removeEventListener ( 'blur' , that . blurHandler ) ;
244+ that . removeEventListener ( 'focus' , that . focusHandler ) ;
245+ that . removeEventListener ( 'keydown' , that . keydownHandler ) ;
246+ that . removeEventListener ( 'keyup' , that . keyupHandler ) ;
247+ if ( that . autocompleteAttr ) that . setAttribute ( 'autocomplete' , that . autocompleteAttr ) ;
248+ else that . removeAttribute ( 'autocomplete' ) ;
249+ that . sc && that . sc . remove ( ) ; // issue#92 PR#93
250+ that = null ;
251+ }
209252 }
210253 } ;
254+
255+ this . elems = elems ; // PR#40
211256 }
212257 return autoComplete ;
213258} ) ( ) ;
214259
215260( function ( ) {
216- if ( typeof define === 'function' && define . amd )
217- define ( 'autoComplete' , function ( ) { return autoComplete ; } ) ;
218- else if ( typeof module !== 'undefined' && module . exports )
219- module . exports = autoComplete ;
220- else
221- window . autoComplete = autoComplete ;
261+ if ( typeof define === 'function' && define . amd ) define ( 'autoComplete' , function ( ) { return autoComplete ; } ) ;
262+ else if ( typeof module !== 'undefined' && module . exports ) module . exports = autoComplete ;
263+ else window . autoComplete = autoComplete ;
222264} ) ( ) ;
0 commit comments