1(function () { 2 "use strict"; 3 var requests = []; 4 5 var AutoSuggest = { 6 init: function (element, options) { 7 if (!element || !element.nodeType) { 8 console.error('AutoSuggest.init: Invalid element provided'); 9 return; 10 } 11 12 const settings = Object.assign({ 13 'PortalID': -1, 14 'SpakID': -1, 15 'Token': '', 16 'AutoSuggestWordsURL': '', 17 'DidYouMeanURL': '', 18 'btnSearch': null, 19 Container: null, 20 oSeResultatFor: 'Se resultat for:', 21 MinimumWordLength: 3 22 }, options || {}); 23 24 var inputSearch = null; 25 var container = null; 26 27 inputSearch = element; 28 container = settings.Container || element; 29 30 var timeout = null; 31 var previousLi = null; 32 33 inputSearch.addEventListener("keydown", function (e) { 34 var autoSuggestEl = document.getElementById("SokAutoSuggest"); 35 if (e.keyCode == 13 && autoSuggestEl) { 36 var hoverLi = autoSuggestEl.querySelector("li.hover"); 37 if (hoverLi) { 38 e.preventDefault(); 39 inputSearch.value = hoverLi.textContent; 40 hoverLi.click(); 41 } 42 } 43 }); 44 45 inputSearch.addEventListener("keyup", function (e) { 46 if (timeout != null) { 47 clearTimeout(timeout); 48 } 49 50 var autoSuggestEl = document.getElementById("SokAutoSuggest"); 51 52 // Arrow key navigation 53 if ((e.keyCode == 40 || e.keyCode == 38) && autoSuggestEl) { 54 var hoverLi = autoSuggestEl.querySelector("li.hover"); 55 var allLis = autoSuggestEl.querySelectorAll("li"); 56 57 if (hoverLi) { 58 var currentIndex = Array.prototype.indexOf.call(allLis, hoverLi); 59 60 if (currentIndex == allLis.length - 1 && e.keyCode == 40) { 61 // At bottom and pressing down, jump to first 62 if (previousLi) { 63 AutoSuggest.triggerMouseOut(previousLi); 64 } 65 AutoSuggest.triggerMouseOver(allLis[0]); 66 previousLi = allLis[0]; 67 } 68 else if (currentIndex == 0 && e.keyCode == 38) { 69 // At top and pressing up, jump to last 70 if (previousLi) { 71 AutoSuggest.triggerMouseOut(previousLi); 72 } 73 AutoSuggest.triggerMouseOver(allLis[allLis.length - 1]); 74 previousLi = allLis[allLis.length - 1]; 75 } 76 else { 77 // Normal navigation 78 if (previousLi) { 79 AutoSuggest.triggerMouseOut(previousLi); 80 } 81 if (e.keyCode == 38) { 82 AutoSuggest.triggerMouseOver(allLis[currentIndex - 1]); 83 previousLi = allLis[currentIndex - 1]; 84 } else { 85 AutoSuggest.triggerMouseOver(allLis[currentIndex + 1]); 86 previousLi = allLis[currentIndex + 1]; 87 } 88 } 89 } else { 90 // First time user presses arrow in navigation 91 if (allLis.length > 0) { 92 AutoSuggest.triggerMouseOver(allLis[0]); 93 previousLi = allLis[0]; 94 } 95 } 96 } else { 97 previousLi = null; 98 timeout = setTimeout(function () { 99 if (inputSearch.value.length < settings.MinimumWordLength) { 100 var autoSuggestEl = document.getElementById("SokAutoSuggest"); 101 if (autoSuggestEl) { 102 autoSuggestEl.remove(); 103 } 104 } 105 106 if (inputSearch.value.length >= settings.MinimumWordLength) { 107 AutoSuggest.Query(inputSearch, container, settings); 108 } 109 }, 250); 110 } 111 }); 112 }, 113 114 triggerMouseOver: function(element) { 115 element.style.backgroundColor = "#EDEDED"; 116 element.classList.add("hover"); 117 }, 118 119 triggerMouseOut: function(element) { 120 element.style.backgroundColor = ""; 121 element.classList.remove("hover"); 122 }, 123 124 Query: function (inputSearch, container, settings) { 125 if (requests.length > 0) { 126 requests.pop().abort(); 127 } 128 129 // Capture the query as it was when the request was sent. If the user keeps 130 // typing before the response arrives, inputSearch.value would otherwise no 131 // longer match the results, causing mismatched/incorrect highlighting. 132 var queryAtRequestTime = inputSearch.value; 133 134 var xhr = new XMLHttpRequest(); 135 xhr.open("POST", settings.AutoSuggestWordsURL, true); 136 xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8"); 137 xhr.setRequestHeader("PortalId", settings.PortalID); 138 xhr.setRequestHeader("SprakId", settings.SprakID); 139 xhr.setRequestHeader("RequestVerificationToken", settings.Token); 140 141 xhr.onreadystatechange = function() { 142 if (xhr.readyState === XMLHttpRequest.DONE) { 143 if (xhr.status === 200 && inputSearch.value === queryAtRequestTime) { 144 var resultat = JSON.parse(xhr.responseText); 145 AutoSuggest.CreateBox(resultat, queryAtRequestTime, inputSearch, container, settings); 146 } 147 } 148 }; 149 150 xhr.send(JSON.stringify(queryAtRequestTime)); 151 requests.push(xhr); 152 }, 153 154 CreateBox: function (resultat, query, inputSearch, container, settings) { 155 var existingBox = document.getElementById("SokAutoSuggest"); 156 if (existingBox) { 157 existingBox.remove(); 158 } 159 160 if (resultat.length == 0) { 161 AutoSuggest.DidYouMean(inputSearch, container, settings); 162 return false; 163 } 164 165 var offset = AutoSuggest.getOffset(container); 166 var offsetInput = AutoSuggest.getOffset(inputSearch); 167 var width = container.offsetWidth; 168 169 if (offsetInput.left - 25 > offset.left) { 170 // Container is not styled, use inputsearch box 171 container = inputSearch; 172 offset = offsetInput; 173 width = inputSearch.offsetWidth + 3; 174 } 175 else if(!container.offsetHeight){ 176 container = inputSearch; 177 } 178 179 var wrapper = document.createElement("div"); 180 wrapper.id = "SokAutoSuggest"; 181 wrapper.style.width = width + "px"; 182 wrapper.style.position = "absolute"; 183 wrapper.style.top = (offset.top + container.offsetHeight) + "px"; 184 wrapper.style.left = offset.left + "px"; 185 wrapper.style.backgroundColor = "white"; 186 wrapper.style.border = "1px solid #ccc"; 187 wrapper.style.boxShadow = "0 2px 4px rgba(0,0,0,0.1)"; 188 189 var hoverTimeout; 190 wrapper.addEventListener("mouseenter", function () { 191 if (hoverTimeout) { 192 clearTimeout(hoverTimeout); 193 } 194 }); 195 196 wrapper.addEventListener("mouseleave", function () { 197 var that = this; 198 hoverTimeout = setTimeout(function () { 199 that.remove(); 200 }, 1500); 201 }); 202 203 var ul = document.createElement("ul"); 204 ul.style.listStyle = "none"; 205 ul.style.margin = "0"; 206 ul.style.padding = "0"; 207 208 var finnesIListe = false; 209 for (var i = 0; i < resultat.length; i++) { 210 if (resultat[i].toLowerCase() == query.toLowerCase()) { 211 finnesIListe = true; 212 } 213 ul.appendChild(AutoSuggest.LagLi(resultat[i], query, inputSearch, settings)); 214 } 215 if (!finnesIListe) { 216 ul.insertBefore(AutoSuggest.LagLi(query, query, inputSearch, settings), ul.firstChild); 217 } 218 wrapper.appendChild(ul); 219 document.body.appendChild(wrapper); 220 }, 221 222 getOffset: function(element) { 223 if (!element || !element.getBoundingClientRect) { 224 return { top: 0, left: 0 }; 225 } 226 var rect = element.getBoundingClientRect(); 227 var scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0; 228 var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0; 229 return { 230 top: rect.top + scrollTop, 231 left: rect.left + scrollLeft 232 }; 233 }, 234 235 LagLi: function (resultat, query, inputSearch, settings) { 236 var activeElement = document.activeElement; 237 var computedStyle = window.getComputedStyle(activeElement); 238 var padding = computedStyle.padding; 239 var text = AutoSuggest.MarkBold(resultat, query); 240 241 var li = document.createElement("li"); 242 li.className = "resultat"; 243 li.style.padding = padding; 244 li.style.cursor = "pointer"; 245 li.innerHTML = text; 246 247 li.addEventListener("click", function () { 248 inputSearch.value = this.textContent; 249 if (settings.btnSearch) { 250 settings.btnSearch.click(); 251 } 252 var autoSuggestEl = document.getElementById("SokAutoSuggest"); 253 if (autoSuggestEl) { 254 autoSuggestEl.remove(); 255 } 256 }); 257 258 li.addEventListener("mouseenter", function () { 259 this.style.backgroundColor = "#EDEDED"; 260 this.classList.add("hover"); 261 }); 262 263 li.addEventListener("mouseleave", function () { 264 this.style.backgroundColor = ""; 265 this.classList.remove("hover"); 266 }); 267 268 return li; 269 }, 270 271 MarkBold: function (text, val) { 272 // Security: HTML-escape the suggestion text before applying <strong> highlighting, 273 text = AutoSuggest.escapeHtml(text); 274 275 // Match all query words in a single pass over the original (escaped) text. 276 // Replacing word-by-word on the already-modified string is unsafe: once the 277 // first word is wrapped in <strong>, a later word whose letters overlap with 278 // "strong" (e.g. "ro", "on", "tr") can match inside that inserted tag markup 279 // and corrupt/duplicate it, which can make the tags render as literal text. 280 var words = val.split(' ').filter(function (w) { return w.length > 0; }); 281 282 if (words.length === 0) { 283 return text; 284 } 285 286 // Longest words first so a longer match "wins" over a shorter one it contains. 287 var sortedEscaped = words 288 .map(AutoSuggest.escapeRegExp) 289 .sort(function (a, b) { return b.length - a.length; }); 290 291 var regex = new RegExp("(" + sortedEscaped.join("|") + ")", "gi"); 292 return text.replace(regex, "<strong>$1</strong>"); 293 }, 294 295 escapeRegExp: function(string) { 296 return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); 297 }, 298 299 escapeHtml: function (string) { 300 if (string == null) { 301 return ''; 302 } 303 return String(string) 304 .replace(/&/g, '&') 305 .replace(/</g, '<') 306 .replace(/>/g, '>') 307 .replace(/"/g, '"') 308 .replace(/'/g, '''); 309 }, 310 311 DidYouMean: function (inputSearch, container, settings) { 312 if (requests.length > 0) { 313 requests.pop().abort(); 314 } 315 316 if (inputSearch.value.length > settings.MinimumWordLength) { 317 var xhr = new XMLHttpRequest(); 318 xhr.open("POST", settings.DidYouMeanURL, true); 319 xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8"); 320 xhr.setRequestHeader("PortalId", settings.PortalID); 321 xhr.setRequestHeader("SprakId", settings.SprakID); 322 xhr.setRequestHeader("RequestVerificationToken", settings.Token); 323 324 xhr.onreadystatechange = function() { 325 if (xhr.readyState === XMLHttpRequest.DONE) { 326 if (xhr.status === 200) { 327 var resultat = JSON.parse(xhr.responseText); 328 var forslag = resultat; 329 if(forslag != null) { 330 AutoSuggest.CreateBox(forslag.Ord, forslag.KorrektOrd, inputSearch, container, settings); 331 } 332 } 333 } 334 }; 335 336 xhr.send(JSON.stringify(inputSearch.value)); 337 requests.push(xhr); 338 } 339 } 340 }; 341 342 // Public API 343 window.AutoSuggest = { 344 init: function(selector, options) { 345 var elements = []; 346 347 if (!selector) { 348 console.error('AutoSuggest: No selector provided'); 349 return; 350 } 351 352 if (typeof selector === 'string') { 353 elements = document.querySelectorAll(selector); 354 if (elements.length === 0) { 355 console.warn('AutoSuggest: No elements found for selector "' + selector + '"'); 356 return; 357 } 358 } else if (selector && selector.nodeType) { 359 elements = [selector]; 360 } else if (selector && selector.length !== undefined) { 361 // Array-like object (NodeList, HTMLCollection, Array) 362 elements = selector; 363 } else { 364 console.error('AutoSuggest: Invalid selector type'); 365 return; 366 } 367 368 Array.prototype.forEach.call(elements, function(element) { 369 if (element && element.nodeType === 1) { // Ensure it's an element node 370 AutoSuggest.init(element, options); 371 } 372 }); 373 } 374 }; 375 376 // Alternative usage pattern similar to jQuery 377 window.autosuggest = function(selector, options) { 378 if (!selector) { 379 console.error('autosuggest: No selector provided'); 380 return; 381 } 382 return window.AutoSuggest.init(selector, options); 383 }; 384 385 // For backward compatibility - wait for DOM to be ready if needed 386 if (typeof document !== 'undefined') { 387 if (document.readyState === 'loading') { 388
388 document.addEventListener('DOMContentLoaded', function() { 389 // DOM is ready 390 }); 391 } 392 } 393})();
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.