diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..9bea433
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+
+.DS_Store
diff --git a/auto-complete.css b/auto-complete.css
index 4261b1d..5db9b08 100644
--- a/auto-complete.css
+++ b/auto-complete.css
@@ -1,9 +1,19 @@
.autocomplete-suggestions {
text-align: left; cursor: default; border: 1px solid #ccc; border-top: 0; background: #fff; box-shadow: -1px 1px 3px rgba(0,0,0,.1);
-
- /* core styles should not be changed */
- position: absolute; display: none; z-index: 9999; max-height: 254px; overflow: hidden; overflow-y: auto; box-sizing: border-box;
+ position: absolute; /* display: none; z-index: 9999; */ max-height: 254px; overflow: hidden; overflow-y: auto; box-sizing: border-box;
+ left: auto; top: auto; width: 100%; margin: 0; contain: content; /* 1.2.0 */
}
.autocomplete-suggestion { position: relative; padding: 0 .6em; line-height: 23px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 1.02em; color: #333; }
.autocomplete-suggestion b { font-weight: normal; color: #1f8dd6; }
.autocomplete-suggestion.selected { background: #f0f0f0; }
+
+autocomplete-holder {
+ overflow: visible; position: absolute; left: auto; top: auto; width: 100%; height: 0; z-index: 9999; box-sizing: border-box; margin:0; padding:0; border:0; contain: size layout;
+}
+
+autocomplete-holder[position="top"] {
+ margin-top: 0 !important;
+}
+autocomplete-holder[position="top"] .autocomplete-suggestions {
+ transform: translateY(-100%);
+}
\ No newline at end of file
diff --git a/auto-complete.js b/auto-complete.js
index 2f5a88c..7526f89 100644
--- a/auto-complete.js
+++ b/auto-complete.js
@@ -1,31 +1,23 @@
/*
- JavaScript autoComplete v1.0.4
+ JavaScript autoComplete v1.2.0
Copyright (c) 2014 Simon Steinberger / Pixabay
GitHub: https://github.com/Pixabay/JavaScript-autoComplete
License: http://www.opensource.org/licenses/mit-license.php
*/
+// Chrome 41+, Edge 15+, Safari 9+, Firefox 35+, Opera 28+, all mobile browsers
+
var autoComplete = (function(){
// "use strict";
function autoComplete(options){
if (!document.querySelector) return;
// helpers
- function hasClass(el, className){ return el.classList ? el.classList.contains(className) : new RegExp('\\b'+ className+'\\b').test(el.className); }
-
- function addEvent(el, type, handler){
- if (el.attachEvent) el.attachEvent('on'+type, handler); else el.addEventListener(type, handler);
- }
- function removeEvent(el, type, handler){
- // if (el.removeEventListener) not working in IE11
- if (el.detachEvent) el.detachEvent('on'+type, handler); else el.removeEventListener(type, handler);
- }
- function live(elClass, event, cb, context){
- addEvent(context || document, event, function(e){
- var found, el = e.target || e.srcElement;
- while (el && !(found = hasClass(el, elClass))) el = el.parentElement;
- if (found) cb.call(el, e);
- });
+ function live(selector, event, cb, context){
+ context.addEventListener(event, function(e){
+ var t = (e.target || e.srcElement).closest(selector);
+ t && cb.call(t, e);
+ }, true);
}
var o = {
@@ -37,186 +29,292 @@ var autoComplete = (function(){
offsetTop: 1,
cache: 1,
menuClass: '',
- renderItem: function (item, search){
+ clickToShow: 1,
+ closeOnTap: 1,
+ renderItem: function(item, search){
// escape special characters
- search = search.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
- var re = new RegExp("(" + search.split(' ').join('|') + ")", "gi");
- return '
' + item.replace(re, "$1") + '
';
+ var si = document.createElement('div');
+ si.className = 'autocomplete-suggestion';
+ si.setAttribute('data-val', item); // see PR#86
+ try{
+ search = search.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
+ si.innerHTML = item.replace(new RegExp("(" + search.split(' ').join('|') + ")", "gi"), "$1");
+ }catch(e){
+ si.textContent = item;
+ }
+ return si;
+ },
+ renderItems: function (data, search, ipt){
+ var tp = document.createElement('template');
+ var df = tp.content;
+ for (var i = 0; i < data.length; i++) {
+ var item = data[i];
+ var si = o.renderItem(item, search);
+ if (typeof si == 'string') tp.innerHTML += si;
+ else if (si && si.nodeType == 1) df.appendChild(si);
+ }
+ var _sc = ipt.sc;
+ var firstChild;
+ while (firstChild = _sc.firstChild) firstChild.remove();
+ _sc.appendChild(df);
},
- onSelect: function(e, term, item){}
+ onSelect: function(e, term, item){},
+ onRender: function(data, val, ipt){},
};
for (var k in options) { if (options.hasOwnProperty(k)) o[k] = options[k]; }
- // init
- var elems = typeof o.selector == 'object' ? [o.selector] : document.querySelectorAll(o.selector);
- for (var i=0; i 0)
- that.sc.scrollTop = selTop + that.sc.suggestionHeight + scrTop - that.sc.maxHeight;
- else if (selTop < 0)
- that.sc.scrollTop = selTop + scrTop;
+ ipt.sc = document.createElement('div');
+ ipt.sc.className = 'autocomplete-suggestions '+o.menuClass;
+ ipt.sc.style.display = 'none';
+
+ ipt.pivot = document.createElement('autocomplete-holder');
+ ipt.autocompleteAttr = ipt.getAttribute('autocomplete');
+ ipt.setAttribute('autocomplete', 'off');
+ ipt.cache = new Map(); // changed from {} to Map; related to PR#37 PR#38
+ ipt.last_val = '';
+
+ ipt.updateSize = function(){
+ var pivot = ipt.pivot;
+ var rect = ipt.getBoundingClientRect();
+ pivot.style.width = rect.width+'px';
+ // pivot.style.height = rect.height+'px';
+ pivot.style.marginTop = rect.height+'px';
+ // pivot.style.marginBottom = -rect.height+'px';
+ }
+
+ ipt.isSCVisible = function(){
+ var style = ipt.sc.style;
+ return style.display != 'none';
+ }
+ ipt.hideSC = function(){
+ var style = ipt.sc.style;
+ style.display != 'none' && (style.display = 'none');
+ }
+
+ ipt.showSC = function(){
+ var style = ipt.sc.style;
+ style.display == 'none' && (style.display = 'block');
+ }
+
+ ipt.updateSC = function(next){ // see issue mentioned in PR#49
+
+ ipt.updateSize();
+ var pivot = ipt.pivot;
+ var _sc = ipt.sc;
+ (_sc.parentNode == pivot) || pivot.appendChild(_sc);
+ (ipt.nextSibling == pivot) || ipt.parentNode.insertBefore(pivot, ipt);
+ ipt.showSC();
+
+ if (!next) _sc.scrollTop = 0;
+ else {
+ var rectSC = _sc.getBoundingClientRect();
+ if (rectSC.height > 0) {
+ var rectNext = next.getBoundingClientRect();
+ var a = rectNext.top - rectSC.top;
+ var b = rectNext.bottom - rectSC.bottom;
+ if (b > 0) {
+ _sc.scrollTop += b;
+ } else if (a < 0) {
+ _sc.scrollTop += a;
}
+ }
}
- }
- addEvent(window, 'resize', that.updateSC);
- document.body.appendChild(that.sc);
-
- live('autocomplete-suggestion', 'mouseleave', function(e){
- var sel = that.sc.querySelector('.autocomplete-suggestion.selected');
- if (sel) setTimeout(function(){ sel.className = sel.className.replace('selected', ''); }, 20);
- }, that.sc);
-
- live('autocomplete-suggestion', 'mouseover', function(e){
- var sel = that.sc.querySelector('.autocomplete-suggestion.selected');
- if (sel) sel.className = sel.className.replace('selected', '');
- this.className += ' selected';
- }, that.sc);
-
- live('autocomplete-suggestion', 'mousedown', function(e){
- if (hasClass(this, 'autocomplete-suggestion')) { // else outside click
- var v = this.getAttribute('data-val');
- that.value = v;
+
+ };
+ // window.addEventListener('resize', ipt.updateSC);
+
+ // ipt.pivot.appendChild(ipt.sc);
+ ipt.parentNode.insertBefore(ipt.pivot, ipt); // avoid Safari's minor layout bug
+
+ ipt.sc.addEventListener('mouseleave', function(e){
+ var sel = ipt.sc.querySelector('.autocomplete-suggestion.selected');
+ sel.classList.remove('selected');
+ });
+
+ live('.autocomplete-suggestion', 'mouseenter', function(e){
+ var sel = ipt.sc.querySelector('.autocomplete-suggestion.selected');
+ if (sel) sel.classList.remove('selected');
+ this.classList.add('selected');
+ }, ipt.sc);
+
+ live('.autocomplete-suggestion', 'pointerdown', function(e){
+ e.stopPropagation();
+ e.preventDefault();
+ if (!e.button) { // only left click
+ var v = ipt.value = this.getAttribute('data-val');
o.onSelect(e, v, this);
- that.sc.style.display = 'none';
+ o.closeOnTap && ipt.hideSC();
+ }
+ }, ipt.sc);
+
+ var suggest = function(data, val){ // see PR#28
+ if (typeof val == 'string') {
+ (data instanceof Array) && ipt.cache.set(val, data);
+ ipt.triggerSC(data, val, val.length >= o.minChars);
}
- }, that.sc);
-
- that.blurHandler = function(){
- try { var over_sb = document.querySelector('.autocomplete-suggestions:hover'); } catch(e){ var over_sb = 0; }
- if (!over_sb) {
- that.last_val = that.value;
- that.sc.style.display = 'none';
- setTimeout(function(){ that.sc.style.display = 'none'; }, 350); // hide suggestions on fast input
- } else if (that !== document.activeElement) setTimeout(function(){ that.focus(); }, 20);
};
- addEvent(that, 'blur', that.blurHandler);
-
- var suggest = function(data){
- var val = that.value;
- that.cache[val] = data;
- if (data.length && val.length >= o.minChars) {
- var s = '';
- for (var i=0;i 40) && key != 13 && key != 27) {
- var val = that.value;
- if (val.length >= o.minChars) {
- if (val != that.last_val) {
- that.last_val = val;
- clearTimeout(that.timer);
+ // esc
+ else if (key == 27) { ipt.value = ipt.last_val; ipt.hideSC(); }
+ // enter
+ else if (key == 13 || key == 9) {
+ var tsc = ipt.sc;
+ var isVisible = ipt.isSCVisible();
+ var sel = tsc.querySelector('.autocomplete-suggestion.selected');
+ if (sel && isVisible) {
+ o.onSelect(e, sel.getAttribute('data-val'), sel);
+ ipt.hideSC();
+ }
+ if (isVisible) { // PR#8
+ e.preventDefault();
+ }
+ ipt.last_val = ipt.value;
+ }
+ },
+ input(){
+ inputCounter++;
+ },
+ keyup(e){
+
+ if (!inputCounter) return;
+ inputCounter = 0;
+ var key = window.event ? e.keyCode : e.which;
+ if (!key || (key < 35 || key > 40) && key != 13 && key != 27) {
+ var val = ipt.value;
+ ipt.last_val = val;
+ if (val.length >= o.minChars) {
+ ipt.timer && clearTimeout(ipt.timer);
+ ipt.timer = 0;
if (o.cache) {
- if (val in that.cache) { suggest(that.cache[val]); return; }
+ var c = ipt.cache;
+ if (c.has(val)) { suggest(c.get(val), val); return; }
// no requests if previous suggestions were empty
- for (var i=1; i= k;) {
+ var part = val.slice(0, j);
+ if (c.has(part) && !c.get(part).length) { suggest([], val); return; }
}
}
- that.timer = setTimeout(function(){ o.source(val, suggest) }, o.delay);
+ // PR#5
+ ipt.timer = setTimeout(function(){
+ var thisRequestId = ++ipt._currentRequestId;
+ var _val = ipt.value;
+ o.source(_val, function (data, val){
+ if (thisRequestId == ipt._currentRequestId){
+ suggest(data, typeof val === 'string' ? val : _val);
+ }
+ });
+ }, o.delay);
+ } else {
+ ipt.sc.style.display = 'none';
}
- } else {
- that.last_val = val;
- that.sc.style.display = 'none';
+ }
+ },
+ // focus(e){
+ // if (!o.minChars) {
+ // ipt.last_val = '\n';
+ // ipt.iptHandlers.keyup(e);
+ // }
+ // },
+
+ click(){
+ if (o.clickToShow && ipt.isContentNotEmpty()) {
+ ipt.showSC();
}
}
- };
- addEvent(that, 'keyup', that.keyupHandler);
- that.focusHandler = function(e){
- that.last_val = '\n';
- that.keyupHandler(e)
- };
- if (!o.minChars) addEvent(that, 'focus', that.focusHandler);
+ }
+
+ for (var h of Object.keys(ipt.iptHandlers)) {
+ ipt.addEventListener(h, ipt.iptHandlers[h]);
+ }
+
+ ipt.destroyAutoComplete = function(){
+ if (ipt) {
+ // window.removeEventListener('resize', ipt.updateSC);
+ for (var h of Object.keys(ipt.iptHandlers)) {
+ ipt.removeEventListener(h, ipt.iptHandlers[h]);
+ }
+ if (ipt.autocompleteAttr) ipt.setAttribute('autocomplete', ipt.autocompleteAttr);
+ else ipt.removeAttribute('autocomplete');
+ ipt.pivot && ipt.pivot.remove(); // issue#92 PR#93
+ ipt = null;
+ }
+ }
+
+ ipt.isContentNotEmpty = function () {
+ return (ipt.value || '').length > 0 && (ipt.sc.textContent || '').length > 0;
+ }
+
+ }
+ // init
+ var elems = typeof o.selector == 'object' ? [o.selector] : document.querySelectorAll(o.selector);
+ for (var i = 0; i < elems.length; i++) {
+ forEach(elems[i]);
}
// public destroy method
this.destroy = function(){
- for (var i=0; i'+e.replace(o,"$1")+""},onSelect:function(){}};for(var c in e)e.hasOwnProperty(c)&&(l[c]=e[c]);for(var a="object"==typeof l.selector?[l.selector]:document.querySelectorAll(l.selector),u=0;u0?i.sc.scrollTop=n+i.sc.suggestionHeight+s-i.sc.maxHeight:0>n&&(i.sc.scrollTop=n+s)}else i.sc.scrollTop=0},o(window,"resize",i.updateSC),document.body.appendChild(i.sc),n("autocomplete-suggestion","mouseleave",function(){var e=i.sc.querySelector(".autocomplete-suggestion.selected");e&&setTimeout(function(){e.className=e.className.replace("selected","")},20)},i.sc),n("autocomplete-suggestion","mouseover",function(){var e=i.sc.querySelector(".autocomplete-suggestion.selected");e&&(e.className=e.className.replace("selected","")),this.className+=" selected"},i.sc),n("autocomplete-suggestion","mousedown",function(e){if(t(this,"autocomplete-suggestion")){var o=this.getAttribute("data-val");i.value=o,l.onSelect(e,o,this),i.sc.style.display="none"}},i.sc),i.blurHandler=function(){try{var e=document.querySelector(".autocomplete-suggestions:hover")}catch(t){var e=0}e?i!==document.activeElement&&setTimeout(function(){i.focus()},20):(i.last_val=i.value,i.sc.style.display="none",setTimeout(function(){i.sc.style.display="none"},350))},o(i,"blur",i.blurHandler);var r=function(e){var t=i.value;if(i.cache[t]=e,e.length&&t.length>=l.minChars){for(var o="",s=0;st||t>40)&&13!=t&&27!=t){var o=i.value;if(o.length>=l.minChars){if(o!=i.last_val){if(i.last_val=o,clearTimeout(i.timer),l.cache){if(o in i.cache)return void r(i.cache[o]);for(var s=1;s
-
+