1
1
/*
2
- JavaScript autoComplete v1.0.4
2
+ JavaScript autoComplete v1.1.0
3
3
Copyright (c) 2014 Simon Steinberger / Pixabay
4
4
GitHub: https://github.com/Pixabay/JavaScript-autoComplete
5
5
License: http://www.opensource.org/licenses/mit-license.php
6
6
*/
7
7
8
+ // Chrome 38+, Edge 13+, Safari 8+, Firefox 26+, Opera 25+, all mobile browsers
9
+
8
10
var autoComplete = ( function ( ) {
9
11
// "use strict";
10
12
function autoComplete ( options ) {
11
13
if ( ! document . querySelector ) return ;
12
14
13
15
// 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
- }
23
16
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 ) ;
29
21
}
30
22
31
23
var o = {
@@ -37,31 +29,56 @@ var autoComplete = (function(){
37
29
offsetTop : 1 ,
38
30
cache : 1 ,
39
31
menuClass : '' ,
40
- renderItem : function ( item , search ) {
32
+ renderItem : function ( item , search ) {
41
33
// 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 ) ;
45
60
} ,
46
61
onSelect : function ( e , term , item ) { }
47
62
} ;
48
63
for ( var k in options ) { if ( options . hasOwnProperty ( k ) ) o [ k ] = options [ k ] ; }
49
64
50
65
// init
51
66
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 ) {
54
68
69
+ that . _currentRequestId = 0 ;
55
70
// create suggestions container "sc"
56
71
that . sc = document . createElement ( 'div' ) ;
57
72
that . sc . className = 'autocomplete-suggestions ' + o . menuClass ;
73
+ that . sc . style . display = 'none' ;
58
74
59
75
that . autocompleteAttr = that . getAttribute ( 'autocomplete' ) ;
60
76
that . setAttribute ( 'autocomplete' , 'off' ) ;
61
- that . cache = { } ;
77
+ that . cache = new Map ( ) ; // changed from {} to Map; related to PR#37 PR#38
62
78
that . last_val = '' ;
63
79
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
65
82
var rect = that . getBoundingClientRect ( ) ;
66
83
that . sc . style . left = Math . round ( rect . left + ( window . pageXOffset || document . documentElement . scrollLeft ) + o . offsetLeft ) + 'px' ;
67
84
that . sc . style . top = Math . round ( rect . bottom + ( window . pageYOffset || document . documentElement . scrollTop ) + o . offsetTop ) + 'px' ;
@@ -74,34 +91,34 @@ var autoComplete = (function(){
74
91
if ( ! next ) that . sc . scrollTop = 0 ;
75
92
else {
76
93
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 ) {
78
95
that . sc . scrollTop = selTop + that . sc . suggestionHeight + scrTop - that . sc . maxHeight ;
79
- else if ( selTop < 0 )
96
+ } else if ( selTop < 0 ) {
80
97
that . sc . scrollTop = selTop + scrTop ;
98
+ }
81
99
}
82
100
}
83
- }
84
- addEvent ( window , 'resize' , that . updateSC ) ;
101
+ } ;
102
+ window . addEventListener ( 'resize' , that . updateSC ) ;
85
103
document . body . appendChild ( that . sc ) ;
86
104
87
- live ( 'autocomplete-suggestion' , 'mouseleave' , function ( e ) {
105
+ that . sc . addEventListener ( 'mouseleave' , function ( e ) {
88
106
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
+ } ) ;
91
109
92
- live ( 'autocomplete-suggestion' , 'mouseover ' , function ( e ) {
110
+ live ( 'autocomplete-suggestion' , 'mouseenter ' , function ( e ) {
93
111
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') ;
96
114
} , that . sc ) ;
97
115
98
116
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' ;
105
122
} , that . sc ) ;
106
123
107
124
that . blurHandler = function ( ) {
@@ -112,38 +129,42 @@ var autoComplete = (function(){
112
129
setTimeout ( function ( ) { that . sc . style . display = 'none' ; } , 350 ) ; // hide suggestions on fast input
113
130
} else if ( that !== document . activeElement ) setTimeout ( function ( ) { that . focus ( ) ; } , 20 ) ;
114
131
} ;
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 ) ;
124
145
that . updateSC ( 0 ) ;
125
- }
126
- else
146
+ } else {
127
147
that . sc . style . display = 'none' ;
128
- }
148
+ }
149
+ } ;
129
150
130
151
that . keydownHandler = function ( e ) {
131
152
var key = window . event ? e . keyCode : e . which ;
132
153
// down (40), up (38)
133
154
if ( ( key == 40 || key == 38 ) && that . sc . innerHTML ) {
134
155
var next , sel = that . sc . querySelector ( '.autocomplete-suggestion.selected' ) ;
135
156
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
139
158
} else {
159
+ sel . classList . remove ( 'selected' ) ;
140
160
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 ;
147
168
}
148
169
that . updateSC ( 0 , next ) ;
149
170
return false ;
@@ -152,11 +173,17 @@ var autoComplete = (function(){
152
173
else if ( key == 27 ) { that . value = that . last_val ; that . sc . style . display = 'none' ; }
153
174
// enter
154
175
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
157
184
}
158
185
} ;
159
- addEvent ( that , 'keydown' , that . keydownHandler ) ;
186
+ that . addEventListener ( 'keydown' , that . keydownHandler ) ;
160
187
161
188
that . keyupHandler = function ( e ) {
162
189
var key = window . event ? e . keyCode : e . which ;
@@ -167,56 +194,68 @@ var autoComplete = (function(){
167
194
that . last_val = val ;
168
195
clearTimeout ( that . timer ) ;
169
196
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 ; }
171
199
// 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 ; }
175
204
}
176
205
}
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 ) ;
178
213
}
179
214
} else {
180
215
that . last_val = val ;
181
216
that . sc . style . display = 'none' ;
182
217
}
183
218
}
184
219
} ;
185
- addEvent ( that , 'keyup' , that . keyupHandler ) ;
220
+ that . addEventListener ( 'keyup' , that . keyupHandler ) ;
186
221
187
222
that . focusHandler = function ( e ) {
188
223
that . last_val = '\n' ;
189
224
that . keyupHandler ( e )
190
225
} ;
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 ] ) ;
192
230
}
193
231
194
232
// public destroy method
195
233
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
+ }
209
249
}
210
250
} ;
251
+
252
+ this . elems = elems ; // PR#40
211
253
}
212
254
return autoComplete ;
213
255
} ) ( ) ;
214
256
215
257
( 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 ;
222
261
} ) ( ) ;
0 commit comments