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