Skip to content

Commit c8cad5b

Browse files
committed
1.1.0
1 parent eda3201 commit c8cad5b

File tree

6 files changed

+301
-124
lines changed

6 files changed

+301
-124
lines changed

auto-complete.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
text-align: left; cursor: default; border: 1px solid #ccc; border-top: 0; background: #fff; box-shadow: -1px 1px 3px rgba(0,0,0,.1);
33

44
/* core styles should not be changed */
5-
position: absolute; display: none; z-index: 9999; max-height: 254px; overflow: hidden; overflow-y: auto; box-sizing: border-box;
5+
position: absolute; /*display: none;*/ z-index: 9999; max-height: 254px; overflow: hidden; overflow-y: auto; box-sizing: border-box;
66
}
77
.autocomplete-suggestion { position: relative; padding: 0 .6em; line-height: 23px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 1.02em; color: #333; }
88
.autocomplete-suggestion b { font-weight: normal; color: #1f8dd6; }

auto-complete.js

Lines changed: 128 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,23 @@
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+
810
var 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

Comments
 (0)