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,56 @@ 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 ;
73+ that . sc . style . display = 'none' ;
5874
5975 that . autocompleteAttr = that . getAttribute ( 'autocomplete' ) ;
6076 that . setAttribute ( 'autocomplete' , 'off' ) ;
61- that . cache = { } ;
77+ that . cache = new Map ( ) ; // changed from {} to Map; related to PR#37 PR#38
6278 that . last_val = '' ;
6379
64- that . updateSC = function ( resize , next ) {
80+ that . updateSC = function ( resize , next ) { // see issue mentioned in PR#49
81+ if ( that != document . activeElement ) return ; // issue#51 PR#52
6582 var rect = that . getBoundingClientRect ( ) ;
6683 that . sc . style . left = Math . round ( rect . left + ( window . pageXOffset || document . documentElement . scrollLeft ) + o . offsetLeft ) + 'px' ;
6784 that . sc . style . top = Math . round ( rect . bottom + ( window . pageYOffset || document . documentElement . scrollTop ) + o . offsetTop ) + 'px' ;
@@ -74,34 +91,34 @@ var autoComplete = (function(){
7491 if ( ! next ) that . sc . scrollTop = 0 ;
7592 else {
7693 var scrTop = that . sc . scrollTop , selTop = next . getBoundingClientRect ( ) . top - that . sc . getBoundingClientRect ( ) . top ;
77- if ( selTop + that . sc . suggestionHeight - that . sc . maxHeight > 0 )
94+ if ( selTop + that . sc . suggestionHeight - that . sc . maxHeight > 0 ) {
7895 that . sc . scrollTop = selTop + that . sc . suggestionHeight + scrTop - that . sc . maxHeight ;
79- else if ( selTop < 0 )
96+ } else if ( selTop < 0 ) {
8097 that . sc . scrollTop = selTop + scrTop ;
98+ }
8199 }
82100 }
83- }
84- addEvent ( window , 'resize' , that . updateSC ) ;
101+ } ;
102+ window . addEventListener ( 'resize' , that . updateSC ) ;
85103 document . body . appendChild ( that . sc ) ;
86104
87- live ( 'autocomplete-suggestion' , 'mouseleave' , function ( e ) {
105+ that . sc . addEventListener ( 'mouseleave' , function ( e ) {
88106 var sel = that . sc . querySelector ( '.autocomplete-suggestion.selected' ) ;
89- if ( sel ) setTimeout ( function ( ) { sel . className = sel . className . replace ( 'selected' , ' ') ; } , 20 ) ;
90- } , that . sc ) ;
107+ if ( sel ) setTimeout ( function ( ) { sel . classList . remove ( 'selected' ) ; } , 20 ) ;
108+ } ) ;
91109
92- live ( 'autocomplete-suggestion' , 'mouseover ' , function ( e ) {
110+ live ( 'autocomplete-suggestion' , 'mouseenter ' , function ( e ) {
93111 var sel = that . sc . querySelector ( '.autocomplete-suggestion.selected' ) ;
94- if ( sel ) sel . className = sel . className . replace ( 'selected' , ' ') ;
95- this . className += ' selected';
112+ if ( sel ) sel . classList . remove ( 'selected' ) ;
113+ this . classList . add ( ' selected') ;
96114 } , that . sc ) ;
97115
98116 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- }
117+ e . stopPropagation ( ) ;
118+ var v = this . getAttribute ( 'data-val' ) ;
119+ that . value = v ;
120+ o . onSelect ( e , v , this ) ;
121+ that . sc . style . display = 'none' ;
105122 } , that . sc ) ;
106123
107124 that . blurHandler = function ( ) {
@@ -112,38 +129,42 @@ var autoComplete = (function(){
112129 setTimeout ( function ( ) { that . sc . style . display = 'none' ; } , 350 ) ; // hide suggestions on fast input
113130 } else if ( that !== document . activeElement ) setTimeout ( function ( ) { that . focus ( ) ; } , 20 ) ;
114131 } ;
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 ;
132+ that . addEventListener ( 'blur' , that . blurHandler ) ;
133+
134+ var suggest = function ( data , val ) {
135+ val = val || that . value ; // PR#28
136+ that . cache . set ( val , data ) ;
137+ that . triggerSC ( data , val , val . length >= o . minChars ) ;
138+ } ;
139+
140+ // PR#40
141+ // Optional method to trigger results programatically
142+ that . triggerSC = function ( data , val , b ) {
143+ if ( data . length && b !== false ) {
144+ o . renderItems ( data , ( val || '' ) , that ) ;
124145 that . updateSC ( 0 ) ;
125- }
126- else
146+ } else {
127147 that . sc . style . display = 'none' ;
128- }
148+ }
149+ } ;
129150
130151 that . keydownHandler = function ( e ) {
131152 var key = window . event ? e . keyCode : e . which ;
132153 // down (40), up (38)
133154 if ( ( key == 40 || key == 38 ) && that . sc . innerHTML ) {
134155 var next , sel = that . sc . querySelector ( '.autocomplete-suggestion.selected' ) ;
135156 if ( ! sel ) {
136- next = ( key == 40 ) ? that . sc . querySelector ( '.autocomplete-suggestion' ) : that . sc . childNodes [ that . sc . childNodes . length - 1 ] ; // first : last
137- next . className += ' selected' ;
138- that . value = next . getAttribute ( 'data-val' ) ;
157+ next = ( key == 40 ) ? that . sc . querySelector ( '.autocomplete-suggestion' ) : that . sc . lastChild ; // first : last
139158 } else {
159+ sel . classList . remove ( 'selected' ) ;
140160 next = ( key == 40 ) ? sel . nextSibling : sel . previousSibling ;
141- if ( next ) {
142- sel . className = sel . className . replace ( 'selected' , '' ) ;
143- next . className += ' selected' ;
144- that . value = next . getAttribute ( 'data-val' ) ;
145- }
146- else { sel . className = sel . className . replace ( 'selected' , '' ) ; that . value = that . last_val ; next = 0 ; }
161+ }
162+ if ( next ) {
163+ next . classList . add ( 'selected' ) ;
164+ that . value = next . getAttribute ( 'data-val' ) ;
165+ } else {
166+ that . value = that . last_val ;
167+ next = 0 ;
147168 }
148169 that . updateSC ( 0 , next ) ;
149170 return false ;
@@ -152,11 +173,17 @@ var autoComplete = (function(){
152173 else if ( key == 27 ) { that . value = that . last_val ; that . sc . style . display = 'none' ; }
153174 // enter
154175 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 ) ; }
176+ var tsc = that . sc ;
177+ var isVisible = tsc . style . display != 'none' ;
178+ var sel = tsc . querySelector ( '.autocomplete-suggestion.selected' ) ;
179+ if ( sel && isVisible ) {
180+ o . onSelect ( e , sel . getAttribute ( 'data-val' ) , sel ) ;
181+ setTimeout ( function ( ) { tsc . style . display = 'none' ; } , 20 ) ;
182+ }
183+ if ( isVisible ) e . preventDefault ( ) ; // PR#8
157184 }
158185 } ;
159- addEvent ( that , 'keydown' , that . keydownHandler ) ;
186+ that . addEventListener ( 'keydown' , that . keydownHandler ) ;
160187
161188 that . keyupHandler = function ( e ) {
162189 var key = window . event ? e . keyCode : e . which ;
@@ -167,56 +194,68 @@ var autoComplete = (function(){
167194 that . last_val = val ;
168195 clearTimeout ( that . timer ) ;
169196 if ( o . cache ) {
170- if ( val in that . cache ) { suggest ( that . cache [ val ] ) ; return ; }
197+ var c = that . cache ;
198+ if ( c . has ( val ) ) { suggest ( c . get ( val ) ) ; return ; }
171199 // 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 ; }
200+ var k = o . minChars ;
201+ for ( var j = val . length - 1 ; j >= k ; j -- ) {
202+ var part = val . slice ( 0 , j ) ;
203+ if ( c . has ( part ) && ! c . get ( part ) . length ) { suggest ( [ ] ) ; return ; }
175204 }
176205 }
177- that . timer = setTimeout ( function ( ) { o . source ( val , suggest ) } , o . delay ) ;
206+ // PR#5
207+ that . timer = setTimeout ( function ( ) {
208+ var thisRequestId = ++ that . _currentRequestId ;
209+ o . source ( val , function ( data , val ) {
210+ if ( thisRequestId === that . _currentRequestId ) return suggest ( data , val ) ;
211+ } ) ;
212+ } , o . delay ) ;
178213 }
179214 } else {
180215 that . last_val = val ;
181216 that . sc . style . display = 'none' ;
182217 }
183218 }
184219 } ;
185- addEvent ( that , 'keyup' , that . keyupHandler ) ;
220+ that . addEventListener ( 'keyup' , that . keyupHandler ) ;
186221
187222 that . focusHandler = function ( e ) {
188223 that . last_val = '\n' ;
189224 that . keyupHandler ( e )
190225 } ;
191- if ( ! o . minChars ) addEvent ( that , 'focus' , that . focusHandler ) ;
226+ if ( ! o . minChars ) that . addEventListener ( 'focus' , that . focusHandler ) ;
227+ }
228+ for ( var i = 0 ; i < elems . length ; i ++ ) {
229+ forEach ( elems [ i ] ) ;
192230 }
193231
194232 // public destroy method
195233 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 ;
234+ var elems = this . elems ;
235+ if ( elems ) {
236+ this . elems = null ;
237+ for ( var i = 0 ; i < elems . length ; i ++ ) {
238+ var that = elems [ i ] ;
239+ window . removeEventListener ( 'resize' , that . updateSC ) ;
240+ that . removeEventListener ( 'blur' , that . blurHandler ) ;
241+ that . removeEventListener ( 'focus' , that . focusHandler ) ;
242+ that . removeEventListener ( 'keydown' , that . keydownHandler ) ;
243+ that . removeEventListener ( 'keyup' , that . keyupHandler ) ;
244+ if ( typeof that . autocompleteAttr == 'string' ) that . setAttribute ( 'autocomplete' , that . autocompleteAttr ) ;
245+ else that . removeAttribute ( 'autocomplete' ) ;
246+ that . sc && that . sc . remove ( ) ; // issue#92 PR#93
247+ that = null ;
248+ }
209249 }
210250 } ;
251+
252+ this . elems = elems ; // PR#40
211253 }
212254 return autoComplete ;
213255} ) ( ) ;
214256
215257( 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 ;
258+ if ( typeof define === 'function' && define . amd ) define ( 'autoComplete' , function ( ) { return autoComplete ; } ) ;
259+ else if ( typeof module !== 'undefined' && module . exports ) module . exports = autoComplete ;
260+ else window . autoComplete = autoComplete ;
222261} ) ( ) ;
0 commit comments