1/* 2 JavaScript autoComplete v1.0.4+ 3 McShelby/hugo-theme-relearn#155 4 - PR #46, PR #75: introducing selectorToInsert and anchor to it 5 - sticky dropdown on scrolling 6 McShelby/hugo-theme-relearn#387 7 - don't empty search input if no data-val is given 8 - don't delete search term but close suggestions when suggestions are open 9 - delete search term when suggestions are closed 10 McShelby/hugo-theme-relearn#452 11 - register focus event ignoring minChars because that doesn't make sense 12 McShelby/hugo-theme-relearn#452 13 - on ESC, close overlay without deleting search term if overlay is open 14 - on ESC, delete search term if overlay is closed 15 - on UP, preventDefault to keep cursor in position 16 17 Copyright (c) 2014 Simon Steinberger / Pixabay 18 GitHub: https://github.com/Pixabay/JavaScript-autoComplete 19 License: http://www.opensource.org/licenses/mit-license.php 20*/ 21 22var autoComplete = (function(){ 23 // "use strict"; 24 function autoComplete(options){ 25 if (!document.querySelector) return; 26 27 // helpers 28 function hasClass(el, className){ return el.classList ? el.classList.contains(className) : new RegExp('\\b'+ className+'\\b').test(el.className); } 29 30 function addEvent(el, type, handler){ 31 if (el.attachEvent) el.attachEvent('on'+type, handler); else el.addEventListener(type, handler); 32 } 33 function removeEvent(el, type, handler){ 34 if (el.detachEvent) el.detachEvent('on'+type, handler); else el.removeEventListener(type, handler); 35 } 36 function live(elClass, event, cb, context){ 37 addEvent(context || document, event, function(e){ 38 var found, el = e.target || e.srcElement; 39 while (el && !(found = hasClass(el, elClass))) el = el.parentElement; 40 if (found) cb.call(el, e); 41 }); 42 } 43 44 var o = { 45 selector: 0, 46 source: 0, 47 minChars: 3, 48 delay: 150, 49 offsetLeft: 0, 50 offsetTop: 1, 51 cache: 1, 52 menuClass: '', 53 selectorToInsert: 0, 54 renderItem: function (item, search){ 55 // escape special characters 56 search = search.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); 57 var re = new RegExp("(" + search.split(' ').join('|') + ")", "gi"); 58 return '<div class="autocomplete-suggestion" data-val="' + item + '">' + item.replace(re, "<b>$1</b>") + '</div>'; 59 }, 60 onSelect: function(e, term, item){} 61 }; 62 for (var k in options) { if (options.hasOwnProperty(k)) o[k] = options[k]; } 63 64 // init 65 var elems = typeof o.selector == 'object' ? [o.selector] : document.querySelectorAll(o.selector); 66 for (var i=0; i<elems.length; i++) { 67 var that = elems[i]; 68 69 // create suggestions container "sc" 70 that.sc = document.createElement('div'); 71 that.sc.className = 'autocomplete-suggestions '+o.menuClass; 72 73 that.autocompleteAttr = that.getAttribute('autocomplete'); 74 that.setAttribute('autocomplete', 'off'); 75 that.cache = {}; 76 that.last_val = ''; 77 78 var parentElement; 79 if (typeof o.selectorToInsert === "string" && document.querySelector(o.selectorToInsert) instanceof HTMLElement) { 80 parentElement = document.querySelector(o.selectorToInsert); 81 } 82 that.updateSC = function(resize, next){ 83 var rect = that.getBoundingClientRect(); 84 var parentOffsetLeft = 0; 85 var parentOffsetTop = 0; 86 var pageXOffset = 0; 87 var pageYOffset = 0; 88 if (parentElement != false) { 89 parentOffsetLeft = parentElement.getBoundingClientRect().left; 90 parentOffsetTop = parentElement.getBoundingClientRect().top; 91 } else { 92 pageXOffset = window.pageXOffset || document.documentElement.scrollLeft; 93 pageYOffset = window.pageYOffset || document.documentElement.scrollTop; 94 } 95 // Is this really the job of the tool or should it be defered to the user? 96 // that.sc.style.left = Math.round(rect.left + pageXOffset + o.offsetLeft - parentOffsetLeft) + 'px'; 97 // that.sc.style.top = Math.round(rect.bottom + pageYOffset + o.offsetTop - parentOffsetTop) + 'px'; 98 // that.sc.style.width = Math.round(rect.right - rect.left) + 'px'; // outerWidth 99 if (!resize) { 100 that.sc.style.display = 'block'; 101 if (!that.sc.maxHeight) { that.sc.maxHeight = parseInt((window.getComputedStyle ? getComputedStyle(that.sc, null) : that.sc.currentStyle).maxHeight); } 102 if (!that.sc.suggestionHeight) that.sc.suggestionHeight = that.sc.querySelector('.autocomplete-suggestion').offsetHeight; 103 if (that.sc.suggestionHeight) 104 if (!next) that.sc.scrollTop = 0; 105 else { 106 var scrTop = that.sc.scrollTop, selTop = next.getBoundingClientRect().top - that.sc.getBoundingClientRect().top; 107 if (selTop + that.sc.suggestionHeight - that.sc.maxHeight > 0) 108 that.sc.scrollTop = selTop + that.sc.suggestionHeight + scrTop - that.sc.maxHeight; 109 else if (selTop < 0) 110 that.sc.scrollTop = selTop + scrTop; 111 } 112 } 113 } 114 addEvent(window, 'resize', that.updateSC); 115 116 if (typeof o.selectorToInsert === "string" && document.querySelector(o.selectorToInsert) instanceof HTMLElement) { 117 document.querySelector(o.selectorToInsert).appendChild(that.sc); 118 } else { 119 document.body.appendChild(that.sc); 120 } 121 122 live('autocomplete-suggestion', 'mouseleave', function(e){ 123 var sel = that.sc.querySelector('.autocomplete-suggestion.selected'); 124 if (sel) setTimeout(function(){ sel.className = sel.className.replace('selected', ''); }, 20); 125 }, that.sc); 126 127 live('autocomplete-suggestion', 'mouseover', function(e){ 128 var sel = that.sc.querySelector('.autocomplete-suggestion.selected'); 129 if (sel) sel.className = sel.className.replace('selected', ''); 130 this.className += ' selected'; 131 }, that.sc); 132 133 live('autocomplete-suggestion', 'mousedown', function(e){ 134 if (hasClass(this, 'autocomplete-suggestion')) { // else outside click 135 var v = this.getAttribute('data-val'); 136 that.value = v; 137 o.onSelect(e, v, this); 138 that.sc.style.display = 'none'; 139 } 140 }, that.sc); 141 142 that.blurHandler = function(){ 143 try { var over_sb = document.querySelector('.autocomplete-suggestions:hover'); } catch(e){ var over_sb = 0; } 144 if (!over_sb) { 145 that.last_val = that.value; 146 that.sc.style.display = 'none'; 147 setTimeout(function(){ that.sc.style.display = 'none'; }, 350); // hide suggestions on fast input 148 } else if (that !== document.activeElement) setTimeout(function(){ that.focus(); }, 20); 149 }; 150 addEvent(that, 'blur', that.blurHandler); 151 152 var suggest = function(data){ 153 var val = that.value; 154 that.cache[val] = data; 155 if (data.length && val.length >= o.minChars) { 156 var s = ''; 157 for (var i=0;i<data.length;i++) s += o.renderItem(data[i], val); 158 that.sc.innerHTML = s; 159 that.updateSC(0); 160 } 161 else 162 that.sc.style.display = 'none'; 163 } 164 165 that.keydownHandler = function(e){ 166 var key = window.event ? e.keyCode : e.which; 167 // down (40), up (38) 168 if ((key == 40 || key == 38) && that.sc.innerHTML) { 169 e.preventDefault(); 170 var next, sel = that.sc.querySelector('.autocomplete-suggestion.selected'); 171 if (!sel) { 172 next = (key == 40) ? that.sc.querySelector('.autocomplete-suggestion') : that.sc.childNodes[that.sc.childNodes.length - 1]; // first : last 173 next.className += ' selected'; 174 if (next.getAttribute('data-val')) that.value = next.getAttribute('data-val'); 175 } else { 176 next = (key == 40) ? sel.nextSibling : sel.previousSibling; 177 if (next) { 178 sel.className = sel.className.replace('selected', ''); 179 next.className += ' selected'; 180 if (next.getAttribute('data-val')) that.value = next.getAttribute('data-val'); 181 } 182 else { 183 sel.className = sel.className.replace('selected', ''); 184 that.value = that.last_val; 185 next = 0; 186 } 187 } 188 that.updateSC(0, next); 189 return false;
190 } 191 // esc 192 else if (key == 27) { 193 if (that.sc.style.display != 'none') { 194 // just close the overlay if it's open, and prevent other listeners 195 // from recognizing it; this is not for you! 196 e.preventDefault(); 197 e.stopImmediatePropagation(); 198 that.sc.style.display = 'none'; 199 var sel = that.sc.querySelector('.autocomplete-suggestion.selected'); 200 if (sel) { 201 that.focus(); 202 } 203 } 204 else { 205 // if no overlay is open, we want to remove the search term and also 206 // want other listeners to recognize it 207 that.value = ''; 208 } 209 } 210 // enter 211 else if (key == 13 || key == 9) { 212 var sel = that.sc.querySelector('.autocomplete-suggestion.selected'); 213 if (sel && that.sc.style.display != 'none') { o.onSelect(e, sel.getAttribute('data-val'), sel); setTimeout(function(){ that.sc.style.display = 'none'; }, 20); } 214 } 215 }; 216 addEvent(that, 'keydown', that.keydownHandler); 217 218 that.keyupHandler = function(e){ 219 var key = window.event ? e.keyCode : e.which; 220 if (!key || (key < 35 || key > 40) && key != 13 && key != 27) { 221 var val = that.value; 222 if (val.length >= o.minChars) { 223 if (val != that.last_val) { 224 that.last_val = val; 225 clearTimeout(that.timer); 226 if (o.cache) { 227 if (val in that.cache) { suggest(that.cache[val]); return; } 228 // no requests if previous suggestions were empty 229 for (var i=1; i<val.length-o.minChars; i++) { 230 var part = val.slice(0, val.length-i); 231 if (part in that.cache && !that.cache[part].length) { suggest([]); return; } 232 } 233 } 234 that.timer = setTimeout(function(){ o.source(val, suggest) }, o.delay); 235 } 236 } else { 237 that.last_val = val; 238 that.sc.style.display = 'none'; 239 } 240 } 241 }; 242 addEvent(that, 'keyup', that.keyupHandler); 243 244 that.focusHandler = function(e){ 245 that.last_val = '\n'; 246 that.keyupHandler(e) 247 }; 248 addEvent(that, 'focus', that.focusHandler); 249 } 250 251 // public destroy method 252 this.destroy = function(){ 253 for (var i=0; i<elems.length; i++) { 254 var that = elems[i]; 255 removeEvent(window, 'resize', that.updateSC); 256 removeEvent(that, 'blur', that.blurHandler); 257 removeEvent(that, 'focus', that.focusHandler); 258 removeEvent(that, 'keydown', that.keydownHandler); 259 removeEvent(that, 'keyup', that.keyupHandler); 260 if (that.autocompleteAttr) 261 that.setAttribute('autocomplete', that.autocompleteAttr); 262 else 263 that.removeAttribute('autocomplete'); 264 try { 265 if (o.selectorToInsert && document.querySelector(o.selectorToInsert).contains(that.sc)) { 266 document.querySelector(o.selectorToInsert).removeChild(that.sc); 267 } else { 268 document.body.removeChild(that.sc); 269 } 270 } catch (error) { 271 console.log('Destroying error: can\'t find target selector', error); 272 throw error; 273 } 274 that = null; 275 } 276 }; 277 } 278 return autoComplete; 279})(); 280 281(function(){ 282 if (typeof define === 'function' && define.amd) 283 define('autoComplete', function () { return autoComplete; }); 284 else if (typeof module !== 'undefined' && module.exports) 285 module.exports = autoComplete; 286 else 287 window.autoComplete = autoComplete; 288})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.