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 var xhr = new XMLHttpRequest(); 130 xhr.open("POST", settings.AutoSuggestWordsURL, true); 131 xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8"); 132 xhr.setRequestHeader("PortalId", settings.PortalID); 133 xhr.setRequestHeader("SprakId", settings.SprakID); 134 xhr.setRequestHeader("RequestVerificationToken", settings.Token); 135 136 xhr.onreadystatechange = function() { 137 if (xhr.readyState === XMLHttpRequest.DONE) { 138 if (xhr.status === 200) { 139 var resultat = JSON.parse(xhr.responseText); 140 AutoSuggest.CreateBox(resultat, inputSearch.value, inputSearch, container, settings); 141 } 142 } 143 }; 144 145 xhr.send(JSON.stringify(inputSearch.value)); 146 requests.push(xhr); 147 }, 148 149 CreateBox: function (resultat, query, inputSearch, container, settings) { 150 var existingBox = document.getElementById("SokAutoSuggest"); 151 if (existingBox) { 152 existingBox.remove(); 153 } 154 155 if (resultat.length == 0) { 156 AutoSuggest.DidYouMean(inputSearch, container, settings); 157 return false; 158 } 159 160 var offset = AutoSuggest.getOffset(container); 161 var offsetInput = AutoSuggest.getOffset(inputSearch); 162 var width = container.offsetWidth; 163 164 if (offsetInput.left - 25 > offset.left) { 165 // Container is not styled, use inputsearch box 166 container = inputSearch; 167 offset = offsetInput; 168 width = inputSearch.offsetWidth + 3; 169 } 170 else if(!container.offsetHeight){ 171 container = inputSearch; 172 } 173 174 var wrapper = document.createElement("div"); 175 wrapper.id = "SokAutoSuggest"; 176 wrapper.style.width = width + "px"; 177 wrapper.style.position = "absolute"; 178 wrapper.style.top = (offset.top + container.offsetHeight) + "px"; 179 wrapper.style.left = offset.left + "px"; 180 wrapper.style.backgroundColor = "white"; 181 wrapper.style.border = "1px solid #ccc"; 182 wrapper.style.boxShadow = "0 2px 4px rgba(0,0,0,0.1)"; 183 184 var hoverTimeout; 185 wrapper.addEventListener("mouseenter", function () { 186 if (hoverTimeout) { 187 clearTimeout(hoverTimeout); 188 } 189 }); 190 191 wrapper.addEventListener("mouseleave", function () { 192 var that = this; 193 hoverTimeout = setTimeout(function () { 194 that.remove(); 195 }, 1500); 196 }); 197 198 var ul = document.createElement("ul"); 199 ul.style.listStyle = "none"; 200 ul.style.margin = "0"; 201 ul.style.padding = "0"; 202 203 var finnesIListe = false; 204 for (var i = 0; i < resultat.length; i++) { 205 if (resultat[i].toLowerCase() == query.toLowerCase()) { 206 finnesIListe = true; 207 } 208 ul.appendChild(AutoSuggest.LagLi(resultat[i], query, inputSearch, settings)); 209 } 210 if (!finnesIListe) { 211 ul.insertBefore(AutoSuggest.LagLi(query, query, inputSearch, settings), ul.firstChild); 212 } 213 wrapper.appendChild(ul); 214 document.body.appendChild(wrapper); 215 }, 216 217 getOffset: function(element) { 218 if (!element || !element.getBoundingClientRect) { 219 return { top: 0, left: 0 }; 220 } 221 var rect = element.getBoundingClientRect(); 222 var scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0; 223 var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0; 224 return { 225 top: rect.top + scrollTop, 226 left: rect.left + scrollLeft 227 }; 228 }, 229 230 LagLi: function (resultat, query, inputSearch, settings) { 231 var activeElement = document.activeElement; 232 var computedStyle = window.getComputedStyle(activeElement); 233 var padding = computedStyle.padding; 234 var text = AutoSuggest.MarkBold(resultat, query); 235 236 var li = document.createElement("li"); 237 li.className = "resultat"; 238 li.style.padding = padding; 239 li.style.cursor = "pointer"; 240 li.innerHTML = text; 241 242 li.addEventListener("click", function () { 243 inputSearch.value = this.textContent; 244 if (settings.btnSearch) { 245 settings.btnSearch.click(); 246 } 247 var autoSuggestEl = document.getElementById("SokAutoSuggest"); 248 if (autoSuggestEl) { 249 autoSuggestEl.remove(); 250 } 251 }); 252 253 li.addEventListener("mouseenter", function () { 254 this.style.backgroundColor = "#EDEDED"; 255 this.classList.add("hover"); 256 }); 257 258 li.addEventListener("mouseleave", function () { 259 this.style.backgroundColor = ""; 260 this.classList.remove("hover"); 261 }); 262 263 return li; 264 }, 265 266 MarkBold: function (text, val) { 267 // Security: HTML-escape the suggestion text before applying <strong> highlighting, 268 text = AutoSuggest.escapeHtml(text); 269 270 if (val.split(' ').length > 1) { 271 var liste = val.split(' '); 272 for (var i = 0; i < liste.length; i++) { 273 var regex = new RegExp("(" + AutoSuggest.escapeRegExp(liste[i]) + ")", "gi"); 274 text = text.replace(regex, "<strong>$1</strong>"); 275 } 276 } else { 277 var regex = new RegExp("(" + AutoSuggest.escapeRegExp(val) + ")", "gi"); 278 text = text.replace(regex, "<strong>$1</strong>"); 279 } 280 return text; 281 }, 282 283 escapeRegExp: function(string) { 284 return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); 285 }, 286 287 escapeHtml: function (string) { 288 if (string == null) { 289 return ''; 290 } 291 return String(string) 292 .replace(/&/g, '&') 293 .replace(/</g, '<') 294 .replace(/>/g, '>') 295 .replace(/"/g, '"') 296 .replace(/'/g, '''); 297 }, 298 299 DidYouMean: function (inputSearch, container, settings) { 300 if (requests.length > 0) { 301 requests.pop().abort(); 302 } 303 304 if (inputSearch.value.length > settings.MinimumWordLength) { 305 var xhr = new XMLHttpRequest(); 306 xhr.open("POST", settings.DidYouMeanURL, true); 307 xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8"); 308 xhr.setRequestHeader("PortalId", settings.PortalID); 309 xhr.setRequestHeader("SprakId", settings.SprakID); 310 xhr.setRequestHeader("RequestVerificationToken", settings.Token); 311 312 xhr.onreadystatechange = function() { 313 if (xhr.readyState === XMLHttpRequest.DONE) { 314 if (xhr.status === 200) { 315 var resultat = JSON.parse(xhr.responseText); 316 var forslag = resultat; 317 if(forslag != null) { 318 AutoSuggest.CreateBox(forslag.Ord, forslag.KorrektOrd, inputSearch, container, settings); 319 } 320 } 321 } 322 }; 323 324 xhr.send(JSON.stringify(inputSearch.value)); 325 requests.push(xhr); 326 } 327 } 328 }; 329 330 // Public API 331 window.AutoSuggest = { 332 init: function(selector, options) { 333 var elements = []; 334 335 if (!selector) { 336 console.error('AutoSuggest: No selector provided'); 337 return; 338 } 339 340 if (typeof selector === 'string') { 341 elements = document.querySelectorAll(selector); 342 if (elements.length === 0) { 343 console.warn('AutoSuggest: No elements found for selector "' + selector + '"'); 344 return; 345 } 346 } else if (selector && selector.nodeType) { 347 elements = [selector]; 348 } else if (selector && selector.length !== undefined) { 349 // Array-like object (NodeList, HTMLCollection, Array) 350 elements = selector; 351 } else { 352 console.error('AutoSuggest: Invalid selector type'); 353 return; 354 } 355 356 Array.prototype.forEach.call(elements, function(element) { 357 if (element && element.nodeType === 1) { // Ensure it's an element node 358 AutoSuggest.init(element, options); 359 } 360 }); 361 } 362 }; 363 364 // Alternative usage pattern similar to jQuery 365 window.autosuggest = function(selector, options) { 366 if (!selector) { 367 console.error('autosuggest: No selector provided'); 368 return; 369 } 370 return window.AutoSuggest.init(selector, options); 371 }; 372 373 // For backward compatibility - wait for DOM to be ready if needed 374 if (typeof document !== 'undefined') { 375 if (document.readyState === 'loading') { 376 document.addEventListener('DOMContentLoaded', function() { 377 // DOM is ready 378 }); 379 } 380 } 381})();
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.