1window.fComplete = (() => { 2 3 class fComplete { 4 5 constructor(selector, options) { 6 let that = this; 7 8 var defaults = { 9 data: [], 10 minChars: 3, 11 maxResults: 10, 12 searchDelay: 200, 13 loadingText: 'Loading...', 14 minCharsText: 'Enter {n} or more characters', 15 noResultsText: 'No results', 16 beforeRender: null, 17 onSelect: null 18 }; 19 20 that.settings = Object.assign({}, defaults, options); 21 that.settings.minChars = Math.max(1, that.settings.minChars); 22 that.settings.maxResults = Math.max(1, that.settings.maxResults); 23 that.settings.searchDelay = Math.max(0, that.settings.searchDelay); 24 25 if ('string' === typeof selector) { 26 var nodes = Array.from(document.querySelectorAll(selector)); 27 } 28 else if (selector instanceof Node) { 29 var nodes = [selector]; 30 } 31 else if (Array.isArray(selector)) { 32 var nodes = selector; 33 } 34 else { 35 var nodes = []; 36 } 37 38 if ('undefined' === typeof window.fCompleteInit) { 39 window.fCompleteInit = { 40 lastFocus: null, 41 eventsBound: true 42 }; 43 that.bindEvents(); 44 } 45 46 nodes.forEach((input) => { 47 that.input = input; 48 input.fcomplete = that; 49 input.classList.add('fcomplete-enabled'); 50 that.create(); 51 }); 52 } 53 54 create() { 55 var that = this; 56 57 var html = ` 58 <div class="fcomplete-wrap fcomplete-hidden"> 59 <div class="fcomplete-status"></div> 60 <div class="fcomplete-results"></div> 61 </div> 62 `; 63 64 var rect = that.input.getBoundingClientRect(); 65 66 var tpl = document.createElement('template'); 67 tpl.innerHTML = html; 68 var wrap = tpl.content.querySelector('.fcomplete-wrap'); 69 wrap.style.minWidth = rect.width + 'px'; 70 that.input.parentNode.insertBefore(wrap, that.input.nextSibling); 71 72 // add a relationship link 73 that.input._rel = wrap; 74 wrap._rel = that.input; 75 } 76 77 destroy() { 78 this.input._rel.remove(); 79 delete this.input._rel; 80 } 81 82 reload() { 83 this.destroy(); 84 this.create(); 85 } 86 87 open() { 88 this.input._rel.classList.remove('fcomplete-hidden'); 89 } 90 91 close() { 92 window.fCompleteInit.lastFocus = null; 93 this.input._rel.classList.add('fcomplete-hidden'); 94 } 95 96 setStatus(text) { 97 var text = text.replace('{n}', this.settings.minChars); 98 var node = this.input._rel.querySelector('.fcomplete-status'); 99 node.innerHTML = text; 100 101 var method = (text) ? 'remove' : 'add'; 102 node.classList[method]('fcomplete-hidden'); 103 } 104 105 render(data) { 106 var data = (this.settings.beforeRender) ? this.settings.beforeRender(data) : data; 107 var wrap = this.input._rel; 108 109 if (data.length) { 110 var html = ''; 111 var len = Math.min(data.length, this.settings.maxResults); 112 113 for (var i = 0; i < len; i++) { 114 html += `<div class="fcomplete-result" data-value="${data[i].value}" tabindex="-1">${data[i].label}</div>`; 115 } 116 117 wrap.querySelector('.fcomplete-results').innerHTML = html; 118 this.setStatus(''); 119 } 120 else { 121 wrap.querySelector('.fcomplete-results').innerHTML = ''; 122 this.setStatus(this.settings.noResultsText); 123 } 124 125 this.input.fcomplete.open(); 126 } 127 128 getAdjacentSibling(which) { 129 var that = this; 130 var which = which || 'next'; 131 var sibling = window.fCompleteInit.lastFocus; 132 var selector = '.fcomplete-result'; 133 134 if (sibling) { 135 sibling = sibling[which + 'ElementSibling']; 136 137 while (sibling) { 138 if (sibling.matches(selector)) break; 139 sibling = sibling[which + 'ElementSibling']; 140 } 141 142 return sibling; 143 } 144 else if ('next' == which) { 145 sibling = that.input._rel.querySelector(selector); 146 } 147 148 return sibling; 149 } 150 151 debounce(func, wait) { 152 var timeout; 153 return (...args) => { 154 var boundFunc = func.bind(this, ...args); 155 clearTimeout(timeout); 156 timeout = setTimeout(boundFunc, wait); 157 } 158 } 159 160 trigger(eventName, ...args) { 161 document.dispatchEvent(new CustomEvent(eventName, {detail: [...args]})); 162 } 163 164 on(eventName, elementSelector, handler) { 165 document.addEventListener(eventName, function(e) { 166 // loop parent nodes from the target to the delegation node 167 for (var target = e.target; target && target != this; target = target.parentNode) { 168 if (target.matches(elementSelector)) { 169 handler.call(target, e); 170 break; 171 } 172 } 173 }, false); 174 } 175 176 bindEvents() { 177 let that = this; 178 179 that.on('click', '*', function(e) { 180 var wrap = this.closest('.fcomplete-wrap'); 181 var isInput = this.classList.contains('fcomplete-enabled'); 182 183 if (isInput) { 184 var input = this; 185 var settings = input.fcomplete.settings; 186 var status = (settings.minChars > input.value.length) ? settings.minCharsText : ''; 187 input.fcomplete.setStatus(status); 188 this.fcomplete.open(); 189 } 190 else if (!wrap && !isInput) { 191 document.querySelectorAll('.fcomplete-wrap').forEach((node) => node.classList.add('fcomplete-hidden')); 192 } 193 }); 194 195 that.on('click', '.fcomplete-result', function(e) { 196 var wrap = e.target.closest('.fcomplete-wrap'); 197 var input = wrap._rel; 198 input.value = e.target.getAttribute('data-value'); 199 200 if (typeof input.fcomplete.settings.onSelect === 'function') { 201 input.fcomplete.settings.onSelect(); 202 } 203 204 input.fcomplete.close(); 205 }); 206 that.on('keydown', '*', function(e) { 207 var wrap = this.closest('.fcomplete-wrap'); 208 var isInput = this.classList.contains('fcomplete-enabled'); 209 210 if (!wrap && !isInput) return; 211 212 var input = (wrap) ? wrap._rel : this; 213 wrap = (wrap) ? wrap : input._rel; 214 215 if (-1 < [13, 38, 40, 27].indexOf(e.which)) { 216 e.preventDefault(); 217 } 218 219 if (13 == e.which) { // enter 220 if (this.classList.contains('fcomplete-result')) { 221 this.click(); 222 } 223 } 224 else if (38 == e.which) { // up 225 if (this.classList.contains('fcomplete-result')) { 226 var sibling = wrap._rel.fcomplete.getAdjacentSibling('previous'); 227 window.fCompleteInit.lastFocus = sibling; // stop at the search box 228 (sibling) ? sibling.focus() : input.focus(); 229 } 230 } 231 else if (40 == e.which) { // down 232 if (this === input) { 233 var firstResult = wrap.querySelector('.fcomplete-result'); 234 235 if (firstResult) {
236 firstResult.focus(); 237 window.fCompleteInit.lastFocus = firstResult; 238 } 239 } 240 else if (this.classList.contains('fcomplete-result')) { 241 var sibling = wrap._rel.fcomplete.getAdjacentSibling('next'); 242 243 if (sibling) { 244 sibling.focus(); 245 window.fCompleteInit.lastFocus = sibling; // stop at the bottom 246 } 247 } 248 } 249 else if (9 == e.which || 27 == e.which) { // tab, esc 250 wrap._rel.fcomplete.close(); 251 } 252 }); 253 254 that.on('keyup', '.fcomplete-enabled', that.debounce(function(e) { 255 if (-1 < [13, 38, 40, 27].indexOf(e.which)) { 256 return; 257 } 258 259 var input = e.target; 260 var settings = input.fcomplete.settings; 261 262 if (settings.minChars <= input.value.length) { 263 if (Array.isArray(settings.data)) { 264 input.fcomplete.render(settings.data); 265 } 266 else if (typeof settings.data === 'function') { 267 input.fcomplete.setStatus(settings.loadingText); 268 settings.data(); 269 } 270 } 271 else { 272 input.fcomplete.render([]); 273 input.fcomplete.setStatus(settings.minCharsText); 274 } 275 }, that.settings.searchDelay)); 276 } 277 } 278 279 var $ = (selector, options) => new fComplete(selector, options); 280 281 return $; 282 283})();
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.