auto-complete.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. /*
  2. JavaScript autoComplete v1.0.4
  3. Copyright (c) 2014 Simon Steinberger / Pixabay
  4. GitHub: https://github.com/Pixabay/JavaScript-autoComplete
  5. License: http://www.opensource.org/licenses/mit-license.php
  6. */
  7. var autoComplete = (function(){
  8. // "use strict";
  9. function autoComplete(options){
  10. if (!document.querySelector) return;
  11. // helpers
  12. function hasClass(el, className){ return el.classList ? el.classList.contains(className) : new RegExp('\\b'+ className+'\\b').test(el.className); }
  13. function addEvent(el, type, handler){
  14. if (el.attachEvent) el.attachEvent('on'+type, handler); else el.addEventListener(type, handler);
  15. }
  16. function removeEvent(el, type, handler){
  17. // if (el.removeEventListener) not working in IE11
  18. if (el.detachEvent) el.detachEvent('on'+type, handler); else el.removeEventListener(type, handler);
  19. }
  20. function live(elClass, event, cb, context){
  21. addEvent(context || document, event, function(e){
  22. var found, el = e.target || e.srcElement;
  23. while (el && !(found = hasClass(el, elClass))) el = el.parentElement;
  24. if (found) cb.call(el, e);
  25. });
  26. }
  27. var o = {
  28. selector: 0,
  29. source: 0,
  30. minChars: 3,
  31. delay: 150,
  32. offsetLeft: 0,
  33. offsetTop: 1,
  34. cache: 1,
  35. menuClass: '',
  36. renderItem: function (item, search){
  37. // escape special characters
  38. search = search.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
  39. var re = new RegExp("(" + search.split(' ').join('|') + ")", "gi");
  40. return '<div class="autocomplete-suggestion" data-val="' + item + '">' + item.replace(re, "<b>$1</b>") + '</div>';
  41. },
  42. onSelect: function(e, term, item){}
  43. };
  44. for (var k in options) { if (options.hasOwnProperty(k)) o[k] = options[k]; }
  45. // init
  46. var elems = typeof o.selector == 'object' ? [o.selector] : document.querySelectorAll(o.selector);
  47. for (var i=0; i<elems.length; i++) {
  48. var that = elems[i];
  49. // create suggestions container "sc"
  50. that.sc = document.createElement('div');
  51. that.sc.className = 'autocomplete-suggestions '+o.menuClass;
  52. that.autocompleteAttr = that.getAttribute('autocomplete');
  53. that.setAttribute('autocomplete', 'off');
  54. that.cache = {};
  55. that.last_val = '';
  56. that.updateSC = function(resize, next){
  57. var rect = that.getBoundingClientRect();
  58. that.sc.style.left = Math.round(rect.left + (window.pageXOffset || document.documentElement.scrollLeft) + o.offsetLeft) + 'px';
  59. that.sc.style.top = Math.round(rect.bottom + (window.pageYOffset || document.documentElement.scrollTop) + o.offsetTop) + 'px';
  60. that.sc.style.width = Math.round(rect.right - rect.left) + 'px'; // outerWidth
  61. if (!resize) {
  62. that.sc.style.display = 'block';
  63. if (!that.sc.maxHeight) { that.sc.maxHeight = parseInt((window.getComputedStyle ? getComputedStyle(that.sc, null) : that.sc.currentStyle).maxHeight); }
  64. if (!that.sc.suggestionHeight) that.sc.suggestionHeight = that.sc.querySelector('.autocomplete-suggestion').offsetHeight;
  65. if (that.sc.suggestionHeight)
  66. if (!next) that.sc.scrollTop = 0;
  67. else {
  68. var scrTop = that.sc.scrollTop, selTop = next.getBoundingClientRect().top - that.sc.getBoundingClientRect().top;
  69. if (selTop + that.sc.suggestionHeight - that.sc.maxHeight > 0)
  70. that.sc.scrollTop = selTop + that.sc.suggestionHeight + scrTop - that.sc.maxHeight;
  71. else if (selTop < 0)
  72. that.sc.scrollTop = selTop + scrTop;
  73. }
  74. }
  75. }
  76. addEvent(window, 'resize', that.updateSC);
  77. document.body.appendChild(that.sc);
  78. live('autocomplete-suggestion', 'mouseleave', function(e){
  79. var sel = that.sc.querySelector('.autocomplete-suggestion.selected');
  80. if (sel) setTimeout(function(){ sel.className = sel.className.replace('selected', ''); }, 20);
  81. }, that.sc);
  82. live('autocomplete-suggestion', 'mouseover', function(e){
  83. var sel = that.sc.querySelector('.autocomplete-suggestion.selected');
  84. if (sel) sel.className = sel.className.replace('selected', '');
  85. this.className += ' selected';
  86. }, that.sc);
  87. live('autocomplete-suggestion', 'mousedown', function(e){
  88. if (hasClass(this, 'autocomplete-suggestion')) { // else outside click
  89. var v = this.getAttribute('data-val');
  90. that.value = v;
  91. o.onSelect(e, v, this);
  92. that.sc.style.display = 'none';
  93. }
  94. }, that.sc);
  95. that.blurHandler = function(){
  96. try { var over_sb = document.querySelector('.autocomplete-suggestions:hover'); } catch(e){ var over_sb = 0; }
  97. if (!over_sb) {
  98. that.last_val = that.value;
  99. that.sc.style.display = 'none';
  100. setTimeout(function(){ that.sc.style.display = 'none'; }, 350); // hide suggestions on fast input
  101. } else if (that !== document.activeElement) setTimeout(function(){ that.focus(); }, 20);
  102. };
  103. addEvent(that, 'blur', that.blurHandler);
  104. var suggest = function(data){
  105. var val = that.value;
  106. that.cache[val] = data;
  107. if (data.length && val.length >= o.minChars) {
  108. var s = '';
  109. for (var i=0;i<data.length;i++) s += o.renderItem(data[i], val);
  110. that.sc.innerHTML = s;
  111. that.updateSC(0);
  112. }
  113. else
  114. that.sc.style.display = 'none';
  115. }
  116. that.keydownHandler = function(e){
  117. var key = window.event ? e.keyCode : e.which;
  118. // down (40), up (38)
  119. if ((key == 40 || key == 38) && that.sc.innerHTML) {
  120. var next, sel = that.sc.querySelector('.autocomplete-suggestion.selected');
  121. if (!sel) {
  122. next = (key == 40) ? that.sc.querySelector('.autocomplete-suggestion') : that.sc.childNodes[that.sc.childNodes.length - 1]; // first : last
  123. next.className += ' selected';
  124. console.log(next);
  125. that.value = next.getAttribute('data-val');
  126. } else {
  127. next = (key == 40) ? sel.nextSibling : sel.previousSibling;
  128. if (next) {
  129. sel.className = sel.className.replace('selected', '');
  130. next.className += ' selected';
  131. that.value = next.getAttribute('data-val');
  132. }
  133. else { sel.className = sel.className.replace('selected', ''); that.value = that.last_val; next = 0; }
  134. }
  135. that.updateSC(0, next);
  136. return false;
  137. }
  138. // esc
  139. else if (key == 27) { that.value = that.last_val; that.sc.style.display = 'none'; }
  140. // enter
  141. else if (key == 13 || key == 9) {
  142. var sel = that.sc.querySelector('.autocomplete-suggestion.selected');
  143. if (sel && that.sc.style.display != 'none') { o.onSelect(e, sel.getAttribute('data-val'), sel); setTimeout(function(){ that.sc.style.display = 'none'; }, 20); }
  144. }
  145. };
  146. addEvent(that, 'keydown', that.keydownHandler);
  147. that.keyupHandler = function(e){
  148. var key = window.event ? e.keyCode : e.which;
  149. if (!key || (key < 35 || key > 40) && key != 13 && key != 27) {
  150. var val = that.value;
  151. if (val.length >= o.minChars) {
  152. if (val != that.last_val) {
  153. that.last_val = val;
  154. clearTimeout(that.timer);
  155. if (o.cache) {
  156. if (val in that.cache) { suggest(that.cache[val]); return; }
  157. // no requests if previous suggestions were empty
  158. for (var i=1; i<val.length-o.minChars; i++) {
  159. var part = val.slice(0, val.length-i);
  160. if (part in that.cache && !that.cache[part].length) { suggest([]); return; }
  161. }
  162. }
  163. that.timer = setTimeout(function(){ o.source(val, suggest) }, o.delay);
  164. }
  165. } else {
  166. that.last_val = val;
  167. that.sc.style.display = 'none';
  168. }
  169. }
  170. };
  171. addEvent(that, 'keyup', that.keyupHandler);
  172. that.focusHandler = function(e){
  173. that.last_val = '\n';
  174. that.keyupHandler(e)
  175. };
  176. if (!o.minChars) addEvent(that, 'focus', that.focusHandler);
  177. }
  178. // public destroy method
  179. this.destroy = function(){
  180. for (var i=0; i<elems.length; i++) {
  181. var that = elems[i];
  182. removeEvent(window, 'resize', that.updateSC);
  183. removeEvent(that, 'blur', that.blurHandler);
  184. removeEvent(that, 'focus', that.focusHandler);
  185. removeEvent(that, 'keydown', that.keydownHandler);
  186. removeEvent(that, 'keyup', that.keyupHandler);
  187. if (that.autocompleteAttr)
  188. that.setAttribute('autocomplete', that.autocompleteAttr);
  189. else
  190. that.removeAttribute('autocomplete');
  191. document.body.removeChild(that.sc);
  192. that = null;
  193. }
  194. };
  195. }
  196. return autoComplete;
  197. })();
  198. (function(){
  199. if (typeof define === 'function' && define.amd)
  200. define('autoComplete', function () { return autoComplete; });
  201. else if (typeof module !== 'undefined' && module.exports)
  202. module.exports = autoComplete;
  203. else
  204. window.autoComplete = autoComplete;
  205. })();