Skip to content

Commit eaa949e

Browse files
committed
Based from 1.1.0
1 parent eda3201 commit eaa949e

File tree

6 files changed

+300
-118
lines changed

6 files changed

+300
-118
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
2+
.DS_Store

auto-complete.js

Lines changed: 126 additions & 84 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,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

Comments
 (0)