1/* 2 @licstart The following is the entire license notice for the JavaScript code in this file. 3 4 The MIT License (MIT) 5 6 Copyright (C) 1997-2020 by Dimitri van Heesch 7 8 Permission is hereby granted, free of charge, to any person obtaining a copy of this software 9 and associated documentation files (the "Software"), to deal in the Software without restriction, 10 including without limitation the rights to use, copy, modify, merge, publish, distribute, 11 sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 12 furnished to do so, subject to the following conditions: 13 14 The above copyright notice and this permission notice shall be included in all copies or 15 substantial portions of the Software. 16 17 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 18 BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 20 DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 23 @licend The above is the entire license notice for the JavaScript code in this file 24 */ 25function convertToId(search) 26{ 27 var result = ''; 28 for (i=0;i<search.length;i++) 29 { 30 var c = search.charAt(i); 31 var cn = c.charCodeAt(0); 32 if (c.match(/[a-z0-9\u0080-\uFFFF]/)) 33 { 34 result+=c; 35 } 36 else if (cn<16) 37 { 38 result+="_0"+cn.toString(16); 39 } 40 else 41 { 42 result+="_"+cn.toString(16); 43 } 44 } 45 return result; 46} 47 48function getXPos(item) 49{ 50 var x = 0; 51 if (item.offsetWidth) 52 { 53 while (item && item!=document.body) 54 { 55 x += item.offsetLeft; 56 item = item.offsetParent; 57 } 58 } 59 return x; 60} 61 62function getYPos(item) 63{ 64 var y = 0; 65 if (item.offsetWidth) 66 { 67 while (item && item!=document.body) 68 { 69 y += item.offsetTop; 70 item = item.offsetParent; 71 } 72 } 73 return y; 74} 75 76var searchResults = new SearchResults("searchResults"); 77 78/* A class handling everything associated with the search panel. 79 80 Parameters: 81 name - The name of the global variable that will be 82 storing this instance. Is needed to be able to set timeouts. 83 resultPath - path to use for external files 84*/ 85function SearchBox(name, resultsPath, extension) 86{ 87 if (!name || !resultsPath) { alert("Missing parameters to SearchBox."); } 88 if (!extension || extension == "") { extension = ".html"; } 89 90 // ---------- Instance variables 91 this.name = name; 92 this.resultsPath = resultsPath; 93 this.keyTimeout = 0; 94 this.keyTimeoutLength = 500; 95 this.closeSelectionTimeout = 300; 96 this.lastSearchValue = ""; 97 this.lastResultsPage = ""; 98 this.hideTimeout = 0; 99 this.searchIndex = 0; 100 this.searchActive = false; 101 this.extension = extension; 102 103 // ----------- DOM Elements 104 105 this.DOMSearchField = function() 106 { return document.getElementById("MSearchField"); } 107 108 this.DOMSearchSelect = function() 109 { return document.getElementById("MSearchSelect"); } 110 111 this.DOMSearchSelectWindow = function() 112 { return document.getElementById("MSearchSelectWindow"); } 113 114 this.DOMPopupSearchResults = function() 115 { return document.getElementById("MSearchResults"); } 116 117 this.DOMPopupSearchResultsWindow = function() 118 { return document.getElementById("MSearchResultsWindow"); } 119 120 this.DOMSearchClose = function() 121 { return document.getElementById("MSearchClose"); } 122 123 this.DOMSearchBox = function() 124 { return document.getElementById("MSearchBox"); } 125 126 // ------------ Event Handlers 127 128 // Called when focus is added or removed from the search field. 129 this.OnSearchFieldFocus = function(isActive) 130 { 131 this.Activate(isActive); 132 } 133 134 this.OnSearchSelectShow = function() 135 { 136 var searchSelectWindow = this.DOMSearchSelectWindow(); 137 var searchField = this.DOMSearchSelect(); 138 139 var left = getXPos(searchField); 140 var top = getYPos(searchField); 141 top += searchField.offsetHeight; 142 143 // show search selection popup 144 searchSelectWindow.style.display='block'; 145 searchSelectWindow.style.left = left + 'px'; 146 searchSelectWindow.style.top = top + 'px'; 147 148 // stop selection hide timer 149 if (this.hideTimeout) 150 { 151 clearTimeout(this.hideTimeout); 152 this.hideTimeout=0; 153 } 154 return false; // to avoid "image drag" default event 155 } 156 157 this.OnSearchSelectHide = function() 158 { 159 this.hideTimeout = setTimeout(this.name +".CloseSelectionWindow()", 160 this.closeSelectionTimeout); 161 } 162 163 // Called when the content of the search field is changed. 164 this.OnSearchFieldChange = function(evt) 165 { 166 if (this.keyTimeout) // kill running timer 167 { 168 clearTimeout(this.keyTimeout); 169 this.keyTimeout = 0; 170 } 171 172 var e = (evt) ? evt : window.event; // for IE 173 if (e.keyCode==40 || e.keyCode==13) 174 { 175 if (e.shiftKey==1) 176 { 177 this.OnSearchSelectShow(); 178 var win=this.DOMSearchSelectWindow(); 179 for (i=0;i<win.childNodes.length;i++) 180 { 181 var child = win.childNodes[i]; // get span within a 182 if (child.className=='SelectItem') 183 { 184 child.focus(); 185 return; 186 } 187 } 188 return; 189 } 190 else 191 { 192 var elem = searchResults.NavNext(0); 193 if (elem) elem.focus(); 194 } 195 } 196 else if (e.keyCode==27) // Escape out of the search field 197 { 198 this.DOMSearchField().blur(); 199 this.DOMPopupSearchResultsWindow().style.display = 'none'; 200 this.DOMSearchClose().style.display = 'none'; 201 this.lastSearchValue = ''; 202 this.Activate(false); 203 return; 204 } 205 206 // strip whitespaces 207 var searchValue = this.DOMSearchField().value.replace(/ +/g, ""); 208 209 if (searchValue != this.lastSearchValue) // search value has changed 210 { 211 if (searchValue != "") // non-empty search 212 { 213 // set timer for search update 214 this.keyTimeout = setTimeout(this.name + '.Search()', 215 this.keyTimeoutLength); 216 } 217 else // empty search field 218 { 219 this.DOMPopupSearchResultsWindow().style.display = 'none'; 220 this.DOMSearchClose().style.display = 'none'; 221 this.lastSearchValue = ''; 222 } 223 } 224 } 225 226 this.SelectItemCount = function(id) 227 { 228 var count=0; 229 var win=this.DOMSearchSelectWindow(); 230 for (i=0;i<win.childNodes.length;i++) 231 { 232 var child = win.childNodes[i]; // get span within a 233 if (child.className=='SelectItem') 234 { 235 count++; 236 } 237 } 238 return count; 239 } 240 241 this.SelectItemSet = function(id) 242 { 243 var i,j=0; 244 var win=this.DOMSearchSelectWindow(); 245 for (i=0;i<win.childNodes.length;i++) 246 { 247 var child = win.childNodes[i]; // get span within a 248 if (child.className=='SelectItem') 249 { 250 var node = child.firstChild; 251 if (j==id) 252 { 253 node.innerHTML='•'; 254 } 255 else 256 { 257 node.innerHTML=' '; 258 } 259 j++; 260 } 261 } 262 } 263 264 // Called when an search filter selection is made. 265 // set item with index id as the active item 266 this.OnSelectItem = function(id) 267 { 268 this.searchIndex = id; 269 this.SelectItemSet(id); 270 var searchValue = this.DOMSearchField().value.replace(/ +/g, ""); 271 if (searchValue!="" && this.searchActive) // something was found -> do a search 272 { 273 this.Search(); 274 } 275 } 276 277 this.OnSearchSelectKey = function(evt) 278 { 279 var e = (evt) ? evt : window.event; // for IE 280 if (e.keyCode==40 && this.searchIndex<this.SelectItemCount()) // Down 281 { 282 this.searchIndex++; 283 this.OnSelectItem(this.searchIndex); 284 } 285 else if (e.keyCode==38 && this.searchIndex>0) // Up 286 { 287 this.searchIndex--; 288 this.OnSelectItem(this.searchIndex); 289 } 290 else if (e.keyCode==13 || e.keyCode==27) 291 { 292 this.OnSelectItem(this.searchIndex); 293 this.CloseSelectionWindow(); 294 this.DOMSearchField().focus(); 295 } 296 return false; 297 } 298 299 // --------- Actions 300 301 // Closes the results window. 302 this.CloseResultsWindow = function() 303 { 304 this.DOMPopupSearchResultsWindow().style.display = 'none'; 305 this.DOMSearchClose().style.display = 'none'; 306 this.Activate(false); 307 } 308 309 this.CloseSelectionWindow = function() 310 { 311 this.DOMSearchSelectWindow().style.display = 'none'; 312 } 313 314 // Performs a search. 315 this.Search = function() 316 { 317 this.keyTimeout = 0; 318 319 // strip leading whitespace 320 var searchValue = this.DOMSearchField().value.replace(/^ +/, ""); 321 322 var code = searchValue.toLowerCase().charCodeAt(0); 323 var idxChar = searchValue.substr(0, 1).toLowerCase(); 324 if ( 0xD800 <= code && code <= 0xDBFF && searchValue > 1) // surrogate pair 325 { 326 idxChar = searchValue.substr(0, 2); 327 } 328 329 var jsFile; 330 331 var idx = indexSectionsWithContent[this.searchIndex].indexOf(idxChar); 332 if (idx!=-1) 333 { 334 var hexCode=idx.toString(16); 335 jsFile = this.resultsPath + indexSectionNames[this.searchIndex] + '_' + hexCode + '.js'; 336 } 337 338 var loadJS = function(url, impl, loc){ 339 var scriptTag = document.createElement('script'); 340 scriptTag.src = url; 341 scriptTag.onload = impl; 342 scriptTag.onreadystatechange = impl; 343 loc.appendChild(scriptTag); 344 } 345 346 var domPopupSearchResultsWindow = this.DOMPopupSearchResultsWindow(); 347 var domSearchBox = this.DOMSearchBox(); 348 var domPopupSearchResults = this.DOMPopupSearchResults(); 349 var domSearchClose = this.DOMSearchClose(); 350 var resultsPath = this.resultsPath; 351 352 var handleResults = function() { 353 document.getElementById("Loading").style.display="none"; 354 if (typeof searchData !== 'undefined') { 355 createResults(resultsPath); 356 document.getElementById("NoMatches").style.display="none"; 357 } 358 359 searchResults.Search(searchValue); 360 361 if (domPopupSearchResultsWindow.style.display!='block') 362 { 363 domSearchClose.style.display = 'inline-block'; 364 var left = getXPos(domSearchBox) + 150; 365 var top = getYPos(domSearchBox) + 20; 366 domPopupSearchResultsWindow.style.display = 'block'; 367 left -= domPopupSearchResults.offsetWidth; 368 var maxWidth = document.body.clientWidth; 369 var maxHeight = document.body.clientHeight; 370 var width = 300; 371 if (left<10) left=10; 372 if (width+left+8>maxWidth) width=maxWidth-left-8; 373 var height = 400; 374 if (height+top+8>maxHeight) height=maxHeight-top-8; 375 domPopupSearchResultsWindow.style.top = top + 'px'; 376 domPopupSearchResultsWindow.style.left = left + 'px'; 377 domPopupSearchResultsWindow.style.width = width + 'px'; 378 domPopupSearchResultsWindow.style.height = height + 'px'; 379 } 380 } 381 382 if (jsFile) { 383 loadJS(jsFile, handleResults, this.DOMPopupSearchResultsWindow()); 384 } else { 385 handleResults(); 386 } 387 388 this.lastSearchValue = searchValue; 389 } 390 391 // -------- Activation Functions 392 393 // Activates or deactivates the search panel, resetting things to 394 // their default values if necessary. 395 this.Activate = function(isActive) 396 { 397 if (isActive || // open it 398 this.DOMPopupSearchResultsWindow().style.display == 'block' 399 ) 400 { 401 this.DOMSearchBox().className = 'MSearchBoxActive'; 402 this.searchActive = true; 403 } 404 else if (!isActive) // directly remove the panel 405 { 406 this.DOMSearchBox().className = 'MSearchBoxInactive'; 407 this.searchActive = false; 408 this.lastSearchValue = '' 409 this.lastResultsPage = ''; 410 this.DOMSearchField().value = ''; 411 } 412 } 413} 414 415// ----------------------------------------------------------------------- 416 417// The class that handles everything on the search results page. 418function SearchResults(name) 419{ 420 // The number of matches from the last run of <Search()>. 421 this.lastMatchCount = 0; 422 this.lastKey = 0; 423 this.repeatOn = false; 424 425 // Toggles the visibility of the passed element ID. 426 this.FindChildElement = function(id) 427 { 428 var parentElement = document.getElementById(id); 429 var element = parentElement.firstChild; 430 431 while (element && element!=parentElement) 432 { 433 if (element.nodeName.toLowerCase() == 'div' && element.className == 'SRChildren') 434 { 435 return element; 436 } 437 438 if (element.nodeName.toLowerCase() == 'div' && element.hasChildNodes()) 439 { 440 element = element.firstChild; 441 } 442 else if (element.nextSibling) 443 { 444 element = element.nextSibling; 445 } 446 else 447 { 448 do 449 { 450 element = element.parentNode; 451 } 452 while (element && element!=parentElement && !element.nextSibling); 453 454 if (element && element!=parentElement) 455 { 456 element = element.nextSibling; 457 } 458 } 459 } 460 } 461 462 this.Toggle = function(id) 463 { 464 var element = this.FindChildElement(id); 465 if (element) 466 { 467 if (element.style.display == 'block') 468 { 469 element.style.display = 'none'; 470 } 471 else 472 { 473 element.style.display = 'block'; 474 } 475 } 476 } 477 478 // Searches for the passed string. If there is no parameter, 479 // it takes it from the URL query. 480 // 481 // Always returns true, since other documents may try to call it 482 // and that may or may not be possible. 483 this.Search = function(search) 484 { 485 if (!search) // get search word from URL 486 { 487 search = window.location.search; 488 search = search.substring(1); // Remove the leading '?' 489 search = unescape(search); 490 } 491 492 search = search.replace(/^ +/, ""); // strip leading spaces 493 search = search.replace(/ +$/, ""); // strip trailing spaces 494 search = search.toLowerCase(); 495 search = convertToId(search); 496 497 var resultRows = document.getElementsByTagName("div"); 498 var matches = 0; 499 500 var i = 0; 501 while (i < resultRows.length) 502 { 503 var row = resultRows.item(i); 504 if (row.className == "SRResult") 505 { 506 var rowMatchName = row.id.toLowerCase(); 507 rowMatchName = rowMatchName.replace(/^sr\d*_/, ''); // strip 'sr123_' 508 509 if (search.length<=rowMatchName.length && 510 rowMatchName.substr(0, search.length)==search) 511 { 512 row.style.display = 'block'; 513 matches++; 514 } 515 else 516 { 517 row.style.display = 'none'; 518 } 519 } 520 i++; 521 } 522 document.getElementById("Searching").style.display='none'; 523 if (matches == 0) // no results 524 { 525 document.getElementById("NoMatches").style.display='block'; 526 } 527 else // at least one result 528 { 529 document.getElementById("NoMatches").style.display='none'; 530 } 531 this.lastMatchCount = matches; 532 return true; 533 } 534 535 // return the first item with index index or higher that is visible 536 this.NavNext = function(index) 537 { 538 var focusItem; 539 while (1) 540 { 541 var focusName = 'Item'+index; 542 focusItem = document.getElementById(focusName); 543 if (focusItem && focusItem.parentNode.parentNode.style.display=='block') 544 { 545 break; 546 } 547 else if (!focusItem) // last element 548 { 549 break; 550 } 551 focusItem=null; 552 index++; 553 } 554 return focusItem; 555 } 556 557 this.NavPrev = function(index) 558 { 559 var focusItem; 560 while (1) 561 { 562 var focusName = 'Item'+index; 563 focusItem = document.getElementById(focusName); 564 if (focusItem && focusItem.parentNode.parentNode.style.display=='block') 565 { 566 break; 567 } 568 else if (!focusItem) // last element 569 { 570 break; 571 } 572 focusItem=null; 573 index--; 574 } 575 return focusItem; 576 } 577 578 this.ProcessKeys = function(e) 579 { 580 if (e.type == "keydown") 581 { 582 this.repeatOn = false; 583 this.lastKey = e.keyCode; 584 } 585 else if (e.type == "keypress") 586 { 587 if (!this.repeatOn) 588 { 589 if (this.lastKey) this.repeatOn = true; 590 return false; // ignore first keypress after keydown 591 } 592 } 593 else if (e.type == "keyup") 594 { 595 this.lastKey = 0; 596 this.repeatOn = false; 597 } 598 return this.lastKey!=0; 599 } 600 601 this.Nav = function(evt,itemIndex) 602 { 603 var e = (evt) ? evt : window.event; // for IE 604 if (e.keyCode==13) return true; 605 if (!this.ProcessKeys(e)) return false; 606 607 if (this.lastKey==38) // Up 608 { 609 var newIndex = itemIndex-1; 610 var focusItem = this.NavPrev(newIndex); 611 if (focusItem) 612 { 613 var child = this.FindChildElement(focusItem.parentNode.parentNode.id); 614 if (child && child.style.display == 'block') // children visible 615 { 616 var n=0; 617 var tmpElem; 618 while (1) // search for last child 619 { 620 tmpElem = document.getElementById('Item'+newIndex+'_c'+n); 621 if (tmpElem) 622 { 623 focusItem = tmpElem; 624 } 625 else // found it! 626 { 627 break; 628 } 629 n++; 630 } 631 } 632 } 633 if (focusItem) 634 { 635 focusItem.focus(); 636 } 637 else // return focus to search field 638 { 639 document.getElementById("MSearchField").focus(); 640 } 641 } 642 else if (this.lastKey==40) // Down 643 { 644 var newIndex = itemIndex+1; 645 var focusItem; 646 var item = document.getElementById('Item'+itemIndex); 647 var elem = this.FindChildElement(item.parentNode.parentNode.id); 648 if (elem && elem.style.display == 'block') // children visible 649 { 650 focusItem = document.getElementById('Item'+itemIndex+'_c0'); 651 } 652 if (!focusItem) focusItem = this.NavNext(newIndex); 653 if (focusItem) focusItem.focus(); 654 } 655 else if (this.lastKey==39) // Right 656 { 657 var item = document.getElementById('Item'+itemIndex); 658 var elem = this.FindChildElement(item.parentNode.parentNode.id); 659 if (elem) elem.style.display = 'block'; 660 } 661 else if (this.lastKey==37) // Left 662 { 663 var item = document.getElementById('Item'+itemIndex); 664 var elem = this.FindChildElement(item.parentNode.parentNode.id); 665 if (elem) elem.style.display = 'none'; 666 } 667 else if (this.lastKey==27) // Escape 668 { 669 searchBox.CloseResultsWindow(); 670 document.getElementById("MSearchField").focus(); 671 } 672 else if (this.lastKey==13) // Enter 673 { 674 return true; 675 } 676 return false; 677 } 678 679 this.NavChild = function(evt,itemIndex,childIndex) 680 { 681 var e = (evt) ? evt : window.event; // for IE 682 if (e.keyCode==13) return true; 683 if (!this.ProcessKeys(e)) return false; 684 685 if (this.lastKey==38) // Up 686 { 687 if (childIndex>0) 688 { 689 var newIndex = childIndex-1; 690 document.getElementById('Item'+itemIndex+'_c'+newIndex).focus(); 691 } 692 else // already at first child, jump to parent 693 { 694 document.getElementById('Item'+itemIndex).focus(); 695 } 696 } 697 else if (this.lastKey==40) // Down 698 { 699 var newIndex = childIndex+1; 700 var elem = document.getElementById('Item'+itemIndex+'_c'+newIndex); 701 if (!elem) // last child, jump to parent next parent 702 { 703 elem = this.NavNext(itemIndex+1); 704 } 705 if (elem) 706 { 707 elem.focus(); 708 } 709 } 710 else if (this.lastKey==27) // Escape 711 { 712 searchBox.CloseResultsWindow(); 713 document.getElementById("MSearchField").focus(); 714 } 715 else if (this.lastKey==13) // Enter 716 { 717 return true; 718 } 719 return false; 720 } 721} 722 723function setKeyActions(elem,action) 724{ 725 elem.setAttribute('onkeydown',action); 726 elem.setAttribute('onkeypress',action); 727 elem.setAttribute('onkeyup',action); 728} 729 730function setClassAttr(elem,attr) 731{ 732 elem.setAttribute('class',attr); 733 elem.setAttribute('className',attr); 734} 735 736function createResults(resultsPath) 737{ 738 var results = document.getElementById("SRResults"); 739 results.innerHTML = ''; 740 for (var e=0; e<searchData.length; e++) 741 { 742 var id = searchData[e][0]; 743 var srResult = document.createElement('div'); 744 srResult.setAttribute('id','SR_'+id); 745 setClassAttr(srResult,'SRResult'); 746 var srEntry = document.createElement('div'); 747 setClassAttr(srEntry,'SREntry'); 748 var srLink = document.createElement('a'); 749 srLink.setAttribute('id','Item'+e); 750 setKeyActions(srLink,'return searchResults.Nav(event,'+e+')'); 751 setClassAttr(srLink,'SRSymbol'); 752 srLink.innerHTML = searchData[e][1][0]; 753 srEntry.appendChild(srLink); 754 if (searchData[e][1].length==2) // single result 755 { 756 srLink.setAttribute('href',resultsPath+searchData[e][1][1][0]); 757 srLink.setAttribute('onclick','searchBox.CloseResultsWindow()'); 758 if (searchData[e][1][1][1]) 759 { 760 srLink.setAttribute('target','_parent'); 761 } 762 else 763 { 764 srLink.setAttribute('target','_blank'); 765 } 766 var srScope = document.createElement('span'); 767 setClassAttr(srScope,'SRScope'); 768 srScope.innerHTML = searchData[e][1][1][2]; 769 srEntry.appendChild(srScope); 770 } 771 else // multiple results 772 { 773 srLink.setAttribute('href','javascript:searchResults.Toggle("SR_'+id+'")'); 774 var srChildren = document.createElement('div'); 775 setClassAttr(srChildren,'SRChildren'); 776 for (var c=0; c<searchData[e][1].length-1; c++) 777 { 778 var srChild = document.createElement('a'); 779 srChild.setAttribute('id','Item'+e+'_c'+c); 780 setKeyActions(srChild,'return searchResults.NavChild(event,'+e+','+c+')'); 781 setClassAttr(srChild,'SRScope'); 782 srChild.setAttribute('href',resultsPath+searchData[e][1][c+1][0]); 783 srChild.setAttribute('onclick','searchBox.CloseResultsWindow()'); 784 if (searchData[e][1][c+1][1]) 785 { 786 srChild.setAttribute('target','_parent'); 787 } 788 else 789 { 790 srChild.setAttribute('target','_blank'); 791 } 792 srChild.innerHTML = searchData[e][1][c+1][2]; 793 srChildren.appendChild(srChild); 794 } 795 srEntry.appendChild(srChildren); 796 } 797 srResult.appendChild(srEntry); 798 results.appendChild(srResult); 799 } 800} 801 802function init_search() 803{ 804 var results = document.getElementById("MSearchSelectWindow"); 805 for (var key in indexSectionLabels) 806 { 807 var link = document.createElement('a'); 808 link.setAttribute('class','SelectItem'); 809 link.setAttribute('onclick','searchBox.OnSelectItem('+key+')'); 810 link.href='javascript:void(0)'; 811 link.innerHTML='<span class="SelectionMark"> </span>'+indexSectionLabels[key]; 812 results.appendChild(link); 813 } 814 searchBox.OnSelectItem(0); 815} 816/* @license-end */ 817