ESRMeter taken from http://members.shaw.ca/swstuff/esrmeter.html (find website on internet archive). Offered here with permission.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

215 lines
10 KiB

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