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 76/* A class handling everything associated with the search panel. 77 78 Parameters: 79 name - The name of the global variable that will be 80 storing this instance. Is needed to be able to set timeouts. 81 resultPath - path to use for external files 82*/ 83function SearchBox(name, resultsPath, inFrame, label, extension) 84{ 85 if (!name || !resultsPath) { alert("Missing parameters to SearchBox."); } 86 if (!extension || extension == "") { extension = ".html"; } 87 88 // ---------- Instance variables 89 this.name = name; 90 this.resultsPath = resultsPath; 91 this.keyTimeout = 0; 92 this.keyTimeoutLength = 500; 93 this.closeSelectionTimeout = 300; 94 this.lastSearchValue = ""; 95 this.lastResultsPage = ""; 96 this.hideTimeout = 0; 97 this.searchIndex = 0; 98 this.searchActive = false; 99 this.insideFrame = inFrame; 100 this.searchLabel = label; 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 if (this.insideFrame) 140 { 141 var left = getXPos(searchField); 142 var top = getYPos(searchField); 143 left += searchField.offsetWidth + 6; 144 top += searchField.offsetHeight; 145 146 // show search selection popup 147 searchSelectWindow.style.display='block'; 148 left -= searchSelectWindow.offsetWidth; 149 searchSelectWindow.style.left = left + 'px'; 150 searchSelectWindow.style.top = top + 'px'; 151 } 152 else 153 { 154 var left = getXPos(searchField); 155 var top = getYPos(searchField); 156 top += searchField.offsetHeight; 157 158 // show search selection popup 159 searchSelectWindow.style.display='block'; 160 searchSelectWindow.style.left = left + 'px'; 161 searchSelectWindow.style.top = top + 'px'; 162 } 163 164 // stop selection hide timer 165 if (this.hideTimeout) 166 { 167 clearTimeout(this.hideTimeout); 168 this.hideTimeout=0; 169 } 170 return false; // to avoid "image drag" default event 171 } 172 173 this.OnSearchSelectHide = function() 174 { 175 this.hideTimeout = setTimeout(this.name +".CloseSelectionWindow()", 176 this.closeSelectionTimeout); 177 } 178 179 // Called when the content of the search field is changed. 180 this.OnSearchFieldChange = function(evt) 181 { 182 if (this.keyTimeout) // kill running timer 183 { 184 clearTimeout(this.keyTimeout); 185 this.keyTimeout = 0; 186 } 187 188 var e = (evt) ? evt : window.event; // for IE 189 if (e.keyCode==40 || e.keyCode==13) 190 { 191 if (e.shiftKey==1) 192 { 193 this.OnSearchSelectShow(); 194 var win=this.DOMSearchSelectWindow(); 195 for (i=0;i<win.childNodes.length;i++) 196 { 197 var child = win.childNodes[i]; // get span within a 198 if (child.className=='SelectItem') 199 { 200 child.focus(); 201 return; 202 } 203 } 204 return; 205 } 206 else 207 { 208 window.frames.MSearchResults.postMessage("take_focus", "*"); 209 } 210 } 211 else if (e.keyCode==27) // Escape out of the search field 212 { 213 this.DOMSearchField().blur(); 214 this.DOMPopupSearchResultsWindow().style.display = 'none'; 215 this.DOMSearchClose().style.display = 'none'; 216 this.lastSearchValue = ''; 217 this.Activate(false); 218 return; 219 } 220 221 // strip whitespaces 222 var searchValue = this.DOMSearchField().value.replace(/ +/g, ""); 223 224 if (searchValue != this.lastSearchValue) // search value has changed 225 { 226 if (searchValue != "") // non-empty search 227 { 228 // set timer for search update 229 this.keyTimeout = setTimeout(this.name + '.Search()', 230 this.keyTimeoutLength); 231 } 232 else // empty search field 233 { 234 this.DOMPopupSearchResultsWindow().style.display = 'none'; 235 this.DOMSearchClose().style.display = 'none'; 236 this.lastSearchValue = ''; 237 } 238 } 239 } 240 241 this.SelectItemCount = function(id) 242 { 243 var count=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 count++; 251 } 252 } 253 return count; 254 } 255 256 this.SelectItemSet = function(id) 257 { 258 var i,j=0; 259 var win=this.DOMSearchSelectWindow(); 260 for (i=0;i<win.childNodes.length;i++) 261 { 262 var child = win.childNodes[i]; // get span within a 263 if (child.className=='SelectItem') 264 { 265 var node = child.firstChild; 266 if (j==id) 267 { 268 node.innerHTML='•'; 269 } 270 else 271 { 272 node.innerHTML=' '; 273 } 274 j++; 275 } 276 } 277 } 278 279 // Called when an search filter selection is made. 280 // set item with index id as the active item 281 this.OnSelectItem = function(id) 282 { 283 this.searchIndex = id; 284 this.SelectItemSet(id); 285 var searchValue = this.DOMSearchField().value.replace(/ +/g, ""); 286 if (searchValue!="" && this.searchActive) // something was found -> do a search 287 { 288 this.Search(); 289 } 290 } 291 292 this.OnSearchSelectKey = function(evt) 293 { 294 var e = (evt) ? evt : window.event; // for IE 295 if (e.keyCode==40 && this.searchIndex<this.SelectItemCount()) // Down 296 { 297 this.searchIndex++; 298 this.OnSelectItem(this.searchIndex); 299 } 300 else if (e.keyCode==38 && this.searchIndex>0) // Up 301 { 302 this.searchIndex--; 303 this.OnSelectItem(this.searchIndex); 304 } 305 else if (e.keyCode==13 || e.keyCode==27) 306 { 307 this.OnSelectItem(this.searchIndex); 308 this.CloseSelectionWindow(); 309 this.DOMSearchField().focus(); 310 } 311 return false; 312 } 313 314 // --------- Actions 315 316 // Closes the results window. 317 this.CloseResultsWindow = function() 318 { 319 this.DOMPopupSearchResultsWindow().style.display = 'none'; 320 this.DOMSearchClose().style.display = 'none'; 321 this.Activate(false); 322 } 323 324 this.CloseSelectionWindow = function() 325 { 326 this.DOMSearchSelectWindow().style.display = 'none'; 327 } 328 329 // Performs a search. 330 this.Search = function() 331 { 332 this.keyTimeout = 0; 333 334 // strip leading whitespace 335 var searchValue = this.DOMSearchField().value.replace(/^ +/, ""); 336 337 var code = searchValue.toLowerCase().charCodeAt(0); 338 var idxChar = searchValue.substr(0, 1).toLowerCase(); 339 if ( 0xD800 <= code && code <= 0xDBFF && searchValue > 1) // surrogate pair 340 { 341 idxChar = searchValue.substr(0, 2); 342 } 343 344 var resultsPage; 345 var resultsPageWithSearch; 346 var hasResultsPage; 347 348 var idx = indexSectionsWithContent[this.searchIndex].indexOf(idxChar); 349 if (idx!=-1) 350 { 351 var hexCode=idx.toString(16); 352 resultsPage = this.resultsPath + '/' + indexSectionNames[this.searchIndex] + '_' + hexCode + this.extension; 353 resultsPageWithSearch = resultsPage+'?'+escape(searchValue); 354 hasResultsPage = true; 355 } 356 else // nothing available for this search term 357 { 358 resultsPage = this.resultsPath + '/nomatches' + this.extension; 359 resultsPageWithSearch = resultsPage; 360 hasResultsPage = false; 361 } 362 363 window.frames.MSearchResults.location = resultsPageWithSearch; 364 var domPopupSearchResultsWindow = this.DOMPopupSearchResultsWindow(); 365 366 if (domPopupSearchResultsWindow.style.display!='block') 367 { 368 var domSearchBox = this.DOMSearchBox(); 369 this.DOMSearchClose().style.display = 'inline-block'; 370 if (this.insideFrame) 371 { 372 var domPopupSearchResults = this.DOMPopupSearchResults(); 373 domPopupSearchResultsWindow.style.position = 'relative'; 374 domPopupSearchResultsWindow.style.display = 'block'; 375 var width = document.body.clientWidth - 8; // the -8 is for IE :-( 376 domPopupSearchResultsWindow.style.width = width + 'px'; 377 domPopupSearchResults.style.width = width + 'px'; 378 } 379 else 380 { 381 var domPopupSearchResults = this.DOMPopupSearchResults(); 382 var left = getXPos(domSearchBox) + 150; // domSearchBox.offsetWidth; 383 var top = getYPos(domSearchBox) + 20; // domSearchBox.offsetHeight + 1; 384 domPopupSearchResultsWindow.style.display = 'block'; 385 left -= domPopupSearchResults.offsetWidth; 386 domPopupSearchResultsWindow.style.top = top + 'px'; 387 domPopupSearchResultsWindow.style.left = left + 'px'; 388 } 389 } 390 391 this.lastSearchValue = searchValue; 392 this.lastResultsPage = resultsPage; 393 } 394 395 // -------- Activation Functions 396 397 // Activates or deactivates the search panel, resetting things to 398 // their default values if necessary. 399 this.Activate = function(isActive) 400 { 401 if (isActive || // open it 402 this.DOMPopupSearchResultsWindow().style.display == 'block' 403 ) 404 { 405 this.DOMSearchBox().className = 'MSearchBoxActive'; 406 407 var searchField = this.DOMSearchField(); 408 409 if (searchField.value == this.searchLabel) // clear "Search" term upon entry 410 { 411 searchField.value = ''; 412 this.searchActive = true; 413 } 414 } 415 else if (!isActive) // directly remove the panel 416 { 417 this.DOMSearchBox().className = 'MSearchBoxInactive'; 418 this.DOMSearchField().value = this.searchLabel; 419 this.searchActive = false; 420 this.lastSearchValue = '' 421 this.lastResultsPage = ''; 422 } 423 } 424} 425 426// ----------------------------------------------------------------------- 427 428// The class that handles everything on the search results page. 429function SearchResults(name) 430{ 431 // The number of matches from the last run of <Search()>. 432 this.lastMatchCount = 0; 433 this.lastKey = 0; 434 this.repeatOn = false; 435 436 // Toggles the visibility of the passed element ID. 437 this.FindChildElement = function(id) 438 { 439 var parentElement = document.getElementById(id); 440 var element = parentElement.firstChild; 441 442 while (element && element!=parentElement) 443 { 444 if (element.nodeName.toLowerCase() == 'div' && element.className == 'SRChildren') 445 { 446 return element; 447 } 448 449 if (element.nodeName.toLowerCase() == 'div' && element.hasChildNodes()) 450 { 451 element = element.firstChild; 452 } 453 else if (element.nextSibling) 454 { 455 element = element.nextSibling; 456 } 457 else 458 { 459 do 460 { 461 element = element.parentNode; 462 } 463 while (element && element!=parentElement && !element.nextSibling); 464 465 if (element && element!=parentElement) 466 { 467 element = element.nextSibling; 468 } 469 } 470 } 471 } 472 473 this.Toggle = function(id) 474 { 475 var element = this.FindChildElement(id); 476 if (element) 477 { 478 if (element.style.display == 'block') 479 { 480 element.style.display = 'none'; 481 } 482 else 483 { 484 element.style.display = 'block'; 485 } 486 } 487 } 488 489 // Searches for the passed string. If there is no parameter, 490 // it takes it from the URL query. 491 // 492 // Always returns true, since other documents may try to call it 493 // and that may or may not be possible. 494 this.Search = function(search) 495 { 496 if (!search) // get search word from URL 497 { 498 search = window.location.search; 499 search = search.substring(1); // Remove the leading '?' 500 search = unescape(search); 501 } 502 503 search = search.replace(/^ +/, ""); // strip leading spaces 504 search = search.replace(/ +$/, ""); // strip trailing spaces 505 search = search.toLowerCase(); 506 search = convertToId(search); 507 508 var resultRows = document.getElementsByTagName("div"); 509 var matches = 0; 510 511 var i = 0; 512 while (i < resultRows.length) 513 { 514 var row = resultRows.item(i); 515 if (row.className == "SRResult") 516 { 517 var rowMatchName = row.id.toLowerCase(); 518 rowMatchName = rowMatchName.replace(/^sr\d*_/, ''); // strip 'sr123_' 519 520 if (search.length<=rowMatchName.length && 521 rowMatchName.substr(0, search.length)==search) 522 { 523 row.style.display = 'block'; 524 matches++; 525 } 526 else 527 { 528 row.style.display = 'none'; 529 } 530 } 531 i++; 532 } 533 document.getElementById("Searching").style.display='none'; 534 if (matches == 0) // no results 535 { 536 document.getElementById("NoMatches").style.display='block'; 537 } 538 else // at least one result 539 { 540 document.getElementById("NoMatches").style.display='none'; 541 } 542 this.lastMatchCount = matches; 543 return true; 544 } 545 546 // return the first item with index index or higher that is visible 547 this.NavNext = function(index) 548 { 549 var focusItem; 550 while (1) 551 { 552 var focusName = 'Item'+index; 553 focusItem = document.getElementById(focusName); 554 if (focusItem && focusItem.parentNode.parentNode.style.display=='block') 555 { 556 break; 557 } 558 else if (!focusItem) // last element 559 { 560 break; 561 } 562 focusItem=null; 563 index++; 564 } 565 return focusItem; 566 } 567 568 this.NavPrev = function(index) 569 { 570 var focusItem; 571 while (1) 572 { 573 var focusName = 'Item'+index; 574 focusItem = document.getElementById(focusName); 575 if (focusItem && focusItem.parentNode.parentNode.style.display=='block') 576 { 577 break; 578 } 579 else if (!focusItem) // last element 580 { 581 break; 582 } 583 focusItem=null; 584 index--; 585 } 586 return focusItem; 587 } 588 589 this.ProcessKeys = function(e) 590 { 591 if (e.type == "keydown") 592 { 593 this.repeatOn = false; 594 this.lastKey = e.keyCode; 595 } 596 else if (e.type == "keypress") 597 { 598 if (!this.repeatOn) 599 { 600 if (this.lastKey) this.repeatOn = true; 601 return false; // ignore first keypress after keydown 602 } 603 } 604 else if (e.type == "keyup") 605 { 606 this.lastKey = 0; 607 this.repeatOn = false; 608 } 609 return this.lastKey!=0; 610 } 611 612 this.Nav = function(evt,itemIndex) 613 { 614 var e = (evt) ? evt : window.event; // for IE 615 if (e.keyCode==13) return true; 616 if (!this.ProcessKeys(e)) return false; 617 618 if (this.lastKey==38) // Up 619 { 620 var newIndex = itemIndex-1; 621 var focusItem = this.NavPrev(newIndex); 622 if (focusItem) 623 { 624 var child = this.FindChildElement(focusItem.parentNode.parentNode.id); 625 if (child && child.style.display == 'block') // children visible 626 { 627 var n=0; 628 var tmpElem; 629 while (1) // search for last child 630 { 631 tmpElem = document.getElementById('Item'+newIndex+'_c'+n); 632 if (tmpElem) 633 { 634 focusItem = tmpElem; 635 } 636 else // found it! 637 { 638 break; 639 } 640 n++; 641 } 642 } 643 } 644 if (focusItem) 645 { 646 focusItem.focus(); 647 } 648 else // return focus to search field 649 { 650 parent.document.getElementById("MSearchField").focus(); 651 } 652 } 653 else if (this.lastKey==40) // Down 654 { 655 var newIndex = itemIndex+1; 656 var focusItem; 657 var item = document.getElementById('Item'+itemIndex); 658 var elem = this.FindChildElement(item.parentNode.parentNode.id); 659 if (elem && elem.style.display == 'block') // children visible 660 { 661 focusItem = document.getElementById('Item'+itemIndex+'_c0'); 662 } 663 if (!focusItem) focusItem = this.NavNext(newIndex); 664 if (focusItem) focusItem.focus(); 665 } 666 else if (this.lastKey==39) // Right 667 { 668 var item = document.getElementById('Item'+itemIndex); 669 var elem = this.FindChildElement(item.parentNode.parentNode.id); 670 if (elem) elem.style.display = 'block'; 671 } 672 else if (this.lastKey==37) // Left 673 { 674 var item = document.getElementById('Item'+itemIndex); 675 var elem = this.FindChildElement(item.parentNode.parentNode.id); 676 if (elem) elem.style.display = 'none'; 677 } 678 else if (this.lastKey==27) // Escape 679 { 680 parent.searchBox.CloseResultsWindow(); 681 parent.document.getElementById("MSearchField").focus(); 682 } 683 else if (this.lastKey==13) // Enter 684 { 685 return true; 686 } 687 return false; 688 } 689 690 this.NavChild = function(evt,itemIndex,childIndex) 691 { 692 var e = (evt) ? evt : window.event; // for IE 693 if (e.keyCode==13) return true; 694 if (!this.ProcessKeys(e)) return false; 695 696 if (this.lastKey==38) // Up 697 { 698 if (childIndex>0) 699 { 700 var newIndex = childIndex-1; 701 document.getElementById('Item'+itemIndex+'_c'+newIndex).focus(); 702 } 703 else // already at first child, jump to parent 704 { 705 document.getElementById('Item'+itemIndex).focus(); 706 } 707 } 708 else if (this.lastKey==40) // Down 709 { 710 var newIndex = childIndex+1; 711 var elem = document.getElementById('Item'+itemIndex+'_c'+newIndex); 712 if (!elem) // last child, jump to parent next parent 713 { 714 elem = this.NavNext(itemIndex+1); 715 } 716 if (elem) 717 { 718 elem.focus(); 719 } 720 } 721 else if (this.lastKey==27) // Escape 722 { 723 parent.searchBox.CloseResultsWindow(); 724 parent.document.getElementById("MSearchField").focus(); 725 } 726 else if (this.lastKey==13) // Enter 727 { 728 return true; 729 } 730 return false; 731 } 732} 733 734function setKeyActions(elem,action) 735{ 736 elem.setAttribute('onkeydown',action); 737 elem.setAttribute('onkeypress',action); 738 elem.setAttribute('onkeyup',action); 739} 740 741function setClassAttr(elem,attr) 742{ 743 elem.setAttribute('class',attr); 744 elem.setAttribute('className',attr); 745} 746 747function createResults() 748{ 749 var results = document.getElementById("SRResults"); 750 for (var e=0; e<searchData.length; e++) 751 { 752 var id = searchData[e][0]; 753 var srResult = document.createElement('div'); 754 srResult.setAttribute('id','SR_'+id); 755 setClassAttr(srResult,'SRResult'); 756 var srEntry = document.createElement('div'); 757 setClassAttr(srEntry,'SREntry'); 758 var srLink = document.createElement('a'); 759 srLink.setAttribute('id','Item'+e); 760 setKeyActions(srLink,'return searchResults.Nav(event,'+e+')'); 761 setClassAttr(srLink,'SRSymbol'); 762 srLink.innerHTML = searchData[e][1][0]; 763 srEntry.appendChild(srLink); 764 if (searchData[e][1].length==2) // single result 765 { 766 srLink.setAttribute('href',searchData[e][1][1][0]); 767 if (searchData[e][1][1][1]) 768 { 769 srLink.setAttribute('target','_parent'); 770 } 771 var srScope = document.createElement('span'); 772 setClassAttr(srScope,'SRScope'); 773 srScope.innerHTML = searchData[e][1][1][2]; 774 srEntry.appendChild(srScope); 775 } 776 else // multiple results 777 { 778 srLink.setAttribute('href','javascript:searchResults.Toggle("SR_'+id+'")'); 779 var srChildren = document.createElement('div'); 780 setClassAttr(srChildren,'SRChildren'); 781 for (var c=0; c<searchData[e][1].length-1; c++) 782 { 783 var srChild = document.createElement('a'); 784 srChild.setAttribute('id','Item'+e+'_c'+c); 785 setKeyActions(srChild,'return searchResults.NavChild(event,'+e+','+c+')'); 786 setClassAttr(srChild,'SRScope'); 787 srChild.setAttribute('href',searchData[e][1][c+1][0]); 788 if (searchData[e][1][c+1][1]) 789 { 790 srChild.setAttribute('target','_parent'); 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