2 @licstart The following is the entire license notice for the JavaScript code in this file.
6 Copyright (C) 1997-2020 by Dimitri van Heesch
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:
14 The above copyright notice and this permission notice shall be included in all copies or
15 substantial portions of the Software.
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.
23 @licend The above is the entire license notice for the JavaScript code in this file
25 function convertToId(search)
28 for (i=0;i<search.length;i++)
30 var c = search.charAt(i);
31 var cn = c.charCodeAt(0);
32 if (c.match(/[a-z0-9\u0080-\uFFFF]/))
38 result+="_0"+cn.toString(16);
42 result+="_"+cn.toString(16);
48 function getXPos(item)
53 while (item && item!=document.body)
56 item = item.offsetParent;
62 function getYPos(item)
67 while (item && item!=document.body)
70 item = item.offsetParent;
76 /* A class handling everything associated with the search panel.
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
83 function SearchBox(name, resultsPath, label, extension)
85 if (!name || !resultsPath) { alert("Missing parameters to SearchBox."); }
86 if (!extension || extension == "") { extension = ".html"; }
88 // ---------- Instance variables
90 this.resultsPath = resultsPath;
92 this.keyTimeoutLength = 500;
93 this.closeSelectionTimeout = 300;
94 this.lastSearchValue = "";
95 this.lastResultsPage = "";
98 this.searchActive = false;
99 this.searchLabel = label;
100 this.extension = extension;
102 // ----------- DOM Elements
104 this.DOMSearchField = function()
105 { return document.getElementById("MSearchField"); }
107 this.DOMSearchSelect = function()
108 { return document.getElementById("MSearchSelect"); }
110 this.DOMSearchSelectWindow = function()
111 { return document.getElementById("MSearchSelectWindow"); }
113 this.DOMPopupSearchResults = function()
114 { return document.getElementById("MSearchResults"); }
116 this.DOMPopupSearchResultsWindow = function()
117 { return document.getElementById("MSearchResultsWindow"); }
119 this.DOMSearchClose = function()
120 { return document.getElementById("MSearchClose"); }
122 this.DOMSearchBox = function()
123 { return document.getElementById("MSearchBox"); }
125 // ------------ Event Handlers
127 // Called when focus is added or removed from the search field.
128 this.OnSearchFieldFocus = function(isActive)
130 this.Activate(isActive);
133 this.OnSearchSelectShow = function()
135 var searchSelectWindow = this.DOMSearchSelectWindow();
136 var searchField = this.DOMSearchSelect();
138 var left = getXPos(searchField);
139 var top = getYPos(searchField);
140 top += searchField.offsetHeight;
142 // show search selection popup
143 searchSelectWindow.style.display='block';
144 searchSelectWindow.style.left = left + 'px';
145 searchSelectWindow.style.top = top + 'px';
147 // stop selection hide timer
148 if (this.hideTimeout)
150 clearTimeout(this.hideTimeout);
153 return false; // to avoid "image drag" default event
156 this.OnSearchSelectHide = function()
158 this.hideTimeout = setTimeout(this.name +".CloseSelectionWindow()",
159 this.closeSelectionTimeout);
162 // Called when the content of the search field is changed.
163 this.OnSearchFieldChange = function(evt)
165 if (this.keyTimeout) // kill running timer
167 clearTimeout(this.keyTimeout);
171 var e = (evt) ? evt : window.event; // for IE
172 if (e.keyCode==40 || e.keyCode==13)
176 this.OnSearchSelectShow();
177 var win=this.DOMSearchSelectWindow();
178 for (i=0;i<win.childNodes.length;i++)
180 var child = win.childNodes[i]; // get span within a
181 if (child.className=='SelectItem')
191 window.frames.MSearchResults.postMessage("take_focus", "*");
194 else if (e.keyCode==27) // Escape out of the search field
196 this.DOMSearchField().blur();
197 this.DOMPopupSearchResultsWindow().style.display = 'none';
198 this.DOMSearchClose().style.display = 'none';
199 this.lastSearchValue = '';
200 this.Activate(false);
205 var searchValue = this.DOMSearchField().value.replace(/ +/g, "");
207 if (searchValue != this.lastSearchValue) // search value has changed
209 if (searchValue != "") // non-empty search
211 // set timer for search update
212 this.keyTimeout = setTimeout(this.name + '.Search()',
213 this.keyTimeoutLength);
215 else // empty search field
217 this.DOMPopupSearchResultsWindow().style.display = 'none';
218 this.DOMSearchClose().style.display = 'none';
219 this.lastSearchValue = '';
224 this.SelectItemCount = function(id)
227 var win=this.DOMSearchSelectWindow();
228 for (i=0;i<win.childNodes.length;i++)
230 var child = win.childNodes[i]; // get span within a
231 if (child.className=='SelectItem')
239 this.SelectItemSet = function(id)
242 var win=this.DOMSearchSelectWindow();
243 for (i=0;i<win.childNodes.length;i++)
245 var child = win.childNodes[i]; // get span within a
246 if (child.className=='SelectItem')
248 var node = child.firstChild;
251 node.innerHTML='•';
255 node.innerHTML=' ';
262 // Called when an search filter selection is made.
263 // set item with index id as the active item
264 this.OnSelectItem = function(id)
266 this.searchIndex = id;
267 this.SelectItemSet(id);
268 var searchValue = this.DOMSearchField().value.replace(/ +/g, "");
269 if (searchValue!="" && this.searchActive) // something was found -> do a search
275 this.OnSearchSelectKey = function(evt)
277 var e = (evt) ? evt : window.event; // for IE
278 if (e.keyCode==40 && this.searchIndex<this.SelectItemCount()) // Down
281 this.OnSelectItem(this.searchIndex);
283 else if (e.keyCode==38 && this.searchIndex>0) // Up
286 this.OnSelectItem(this.searchIndex);
288 else if (e.keyCode==13 || e.keyCode==27)
290 this.OnSelectItem(this.searchIndex);
291 this.CloseSelectionWindow();
292 this.DOMSearchField().focus();
299 // Closes the results window.
300 this.CloseResultsWindow = function()
302 this.DOMPopupSearchResultsWindow().style.display = 'none';
303 this.DOMSearchClose().style.display = 'none';
304 this.Activate(false);
307 this.CloseSelectionWindow = function()
309 this.DOMSearchSelectWindow().style.display = 'none';
312 // Performs a search.
313 this.Search = function()
317 // strip leading whitespace
318 var searchValue = this.DOMSearchField().value.replace(/^ +/, "");
320 var code = searchValue.toLowerCase().charCodeAt(0);
321 var idxChar = searchValue.substr(0, 1).toLowerCase();
322 if ( 0xD800 <= code && code <= 0xDBFF && searchValue > 1) // surrogate pair
324 idxChar = searchValue.substr(0, 2);
328 var resultsPageWithSearch;
331 var idx = indexSectionsWithContent[this.searchIndex].indexOf(idxChar);
334 var hexCode=idx.toString(16);
335 resultsPage = this.resultsPath + '/' + indexSectionNames[this.searchIndex] + '_' + hexCode + this.extension;
336 resultsPageWithSearch = resultsPage+'?'+escape(searchValue);
337 hasResultsPage = true;
339 else // nothing available for this search term
341 resultsPage = this.resultsPath + '/nomatches' + this.extension;
342 resultsPageWithSearch = resultsPage;
343 hasResultsPage = false;
346 window.frames.MSearchResults.location = resultsPageWithSearch;
347 var domPopupSearchResultsWindow = this.DOMPopupSearchResultsWindow();
349 if (domPopupSearchResultsWindow.style.display!='block')
351 var domSearchBox = this.DOMSearchBox();
352 this.DOMSearchClose().style.display = 'inline-block';
353 var domPopupSearchResults = this.DOMPopupSearchResults();
354 var left = getXPos(domSearchBox) + 150; // domSearchBox.offsetWidth;
355 var top = getYPos(domSearchBox) + 20; // domSearchBox.offsetHeight + 1;
356 domPopupSearchResultsWindow.style.display = 'block';
357 left -= domPopupSearchResults.offsetWidth;
358 var maxWidth = document.body.clientWidth;
360 if (left<10) left=10;
361 if (width+left+8>maxWidth) width=maxWidth-left-8;
362 domPopupSearchResultsWindow.style.top = top + 'px';
363 domPopupSearchResultsWindow.style.left = left + 'px';
364 domPopupSearchResultsWindow.style.width = width + 'px';
367 this.lastSearchValue = searchValue;
368 this.lastResultsPage = resultsPage;
371 // -------- Activation Functions
373 // Activates or deactivates the search panel, resetting things to
374 // their default values if necessary.
375 this.Activate = function(isActive)
377 if (isActive || // open it
378 this.DOMPopupSearchResultsWindow().style.display == 'block'
381 this.DOMSearchBox().className = 'MSearchBoxActive';
383 var searchField = this.DOMSearchField();
385 if (searchField.value == this.searchLabel) // clear "Search" term upon entry
387 searchField.value = '';
388 this.searchActive = true;
391 else if (!isActive) // directly remove the panel
393 this.DOMSearchBox().className = 'MSearchBoxInactive';
394 this.DOMSearchField().value = this.searchLabel;
395 this.searchActive = false;
396 this.lastSearchValue = ''
397 this.lastResultsPage = '';
402 // -----------------------------------------------------------------------
404 // The class that handles everything on the search results page.
405 function SearchResults(name)
407 // The number of matches from the last run of <Search()>.
408 this.lastMatchCount = 0;
410 this.repeatOn = false;
412 // Toggles the visibility of the passed element ID.
413 this.FindChildElement = function(id)
415 var parentElement = document.getElementById(id);
416 var element = parentElement.firstChild;
418 while (element && element!=parentElement)
420 if (element.nodeName.toLowerCase() == 'div' && element.className == 'SRChildren')
425 if (element.nodeName.toLowerCase() == 'div' && element.hasChildNodes())
427 element = element.firstChild;
429 else if (element.nextSibling)
431 element = element.nextSibling;
437 element = element.parentNode;
439 while (element && element!=parentElement && !element.nextSibling);
441 if (element && element!=parentElement)
443 element = element.nextSibling;
449 this.Toggle = function(id)
451 var element = this.FindChildElement(id);
454 if (element.style.display == 'block')
456 element.style.display = 'none';
460 element.style.display = 'block';
465 // Searches for the passed string. If there is no parameter,
466 // it takes it from the URL query.
468 // Always returns true, since other documents may try to call it
469 // and that may or may not be possible.
470 this.Search = function(search)
472 if (!search) // get search word from URL
474 search = window.location.search;
475 search = search.substring(1); // Remove the leading '?'
476 search = unescape(search);
479 search = search.replace(/^ +/, ""); // strip leading spaces
480 search = search.replace(/ +$/, ""); // strip trailing spaces
481 search = search.toLowerCase();
482 search = convertToId(search);
484 var resultRows = document.getElementsByTagName("div");
488 while (i < resultRows.length)
490 var row = resultRows.item(i);
491 if (row.className == "SRResult")
493 var rowMatchName = row.id.toLowerCase();
494 rowMatchName = rowMatchName.replace(/^sr\d*_/, ''); // strip 'sr123_'
496 if (search.length<=rowMatchName.length &&
497 rowMatchName.substr(0, search.length)==search)
499 row.style.display = 'block';
504 row.style.display = 'none';
509 document.getElementById("Searching").style.display='none';
510 if (matches == 0) // no results
512 document.getElementById("NoMatches").style.display='block';
514 else // at least one result
516 document.getElementById("NoMatches").style.display='none';
518 this.lastMatchCount = matches;
522 // return the first item with index index or higher that is visible
523 this.NavNext = function(index)
528 var focusName = 'Item'+index;
529 focusItem = document.getElementById(focusName);
530 if (focusItem && focusItem.parentNode.parentNode.style.display=='block')
534 else if (!focusItem) // last element
544 this.NavPrev = function(index)
549 var focusName = 'Item'+index;
550 focusItem = document.getElementById(focusName);
551 if (focusItem && focusItem.parentNode.parentNode.style.display=='block')
555 else if (!focusItem) // last element
565 this.ProcessKeys = function(e)
567 if (e.type == "keydown")
569 this.repeatOn = false;
570 this.lastKey = e.keyCode;
572 else if (e.type == "keypress")
576 if (this.lastKey) this.repeatOn = true;
577 return false; // ignore first keypress after keydown
580 else if (e.type == "keyup")
583 this.repeatOn = false;
585 return this.lastKey!=0;
588 this.Nav = function(evt,itemIndex)
590 var e = (evt) ? evt : window.event; // for IE
591 if (e.keyCode==13) return true;
592 if (!this.ProcessKeys(e)) return false;
594 if (this.lastKey==38) // Up
596 var newIndex = itemIndex-1;
597 var focusItem = this.NavPrev(newIndex);
600 var child = this.FindChildElement(focusItem.parentNode.parentNode.id);
601 if (child && child.style.display == 'block') // children visible
605 while (1) // search for last child
607 tmpElem = document.getElementById('Item'+newIndex+'_c'+n);
624 else // return focus to search field
626 parent.document.getElementById("MSearchField").focus();
629 else if (this.lastKey==40) // Down
631 var newIndex = itemIndex+1;
633 var item = document.getElementById('Item'+itemIndex);
634 var elem = this.FindChildElement(item.parentNode.parentNode.id);
635 if (elem && elem.style.display == 'block') // children visible
637 focusItem = document.getElementById('Item'+itemIndex+'_c0');
639 if (!focusItem) focusItem = this.NavNext(newIndex);
640 if (focusItem) focusItem.focus();
642 else if (this.lastKey==39) // Right
644 var item = document.getElementById('Item'+itemIndex);
645 var elem = this.FindChildElement(item.parentNode.parentNode.id);
646 if (elem) elem.style.display = 'block';
648 else if (this.lastKey==37) // Left
650 var item = document.getElementById('Item'+itemIndex);
651 var elem = this.FindChildElement(item.parentNode.parentNode.id);
652 if (elem) elem.style.display = 'none';
654 else if (this.lastKey==27) // Escape
656 parent.searchBox.CloseResultsWindow();
657 parent.document.getElementById("MSearchField").focus();
659 else if (this.lastKey==13) // Enter
666 this.NavChild = function(evt,itemIndex,childIndex)
668 var e = (evt) ? evt : window.event; // for IE
669 if (e.keyCode==13) return true;
670 if (!this.ProcessKeys(e)) return false;
672 if (this.lastKey==38) // Up
676 var newIndex = childIndex-1;
677 document.getElementById('Item'+itemIndex+'_c'+newIndex).focus();
679 else // already at first child, jump to parent
681 document.getElementById('Item'+itemIndex).focus();
684 else if (this.lastKey==40) // Down
686 var newIndex = childIndex+1;
687 var elem = document.getElementById('Item'+itemIndex+'_c'+newIndex);
688 if (!elem) // last child, jump to parent next parent
690 elem = this.NavNext(itemIndex+1);
697 else if (this.lastKey==27) // Escape
699 parent.searchBox.CloseResultsWindow();
700 parent.document.getElementById("MSearchField").focus();
702 else if (this.lastKey==13) // Enter
710 function setKeyActions(elem,action)
712 elem.setAttribute('onkeydown',action);
713 elem.setAttribute('onkeypress',action);
714 elem.setAttribute('onkeyup',action);
717 function setClassAttr(elem,attr)
719 elem.setAttribute('class',attr);
720 elem.setAttribute('className',attr);
723 function createResults()
725 var results = document.getElementById("SRResults");
726 for (var e=0; e<searchData.length; e++)
728 var id = searchData[e][0];
729 var srResult = document.createElement('div');
730 srResult.setAttribute('id','SR_'+id);
731 setClassAttr(srResult,'SRResult');
732 var srEntry = document.createElement('div');
733 setClassAttr(srEntry,'SREntry');
734 var srLink = document.createElement('a');
735 srLink.setAttribute('id','Item'+e);
736 setKeyActions(srLink,'return searchResults.Nav(event,'+e+')');
737 setClassAttr(srLink,'SRSymbol');
738 srLink.innerHTML = searchData[e][1][0];
739 srEntry.appendChild(srLink);
740 if (searchData[e][1].length==2) // single result
742 srLink.setAttribute('href',searchData[e][1][1][0]);
743 srLink.setAttribute('onclick','parent.searchBox.CloseResultsWindow()');
744 if (searchData[e][1][1][1])
746 srLink.setAttribute('target','_parent');
750 srLink.setAttribute('target','_blank');
752 var srScope = document.createElement('span');
753 setClassAttr(srScope,'SRScope');
754 srScope.innerHTML = searchData[e][1][1][2];
755 srEntry.appendChild(srScope);
757 else // multiple results
759 srLink.setAttribute('href','javascript:searchResults.Toggle("SR_'+id+'")');
760 var srChildren = document.createElement('div');
761 setClassAttr(srChildren,'SRChildren');
762 for (var c=0; c<searchData[e][1].length-1; c++)
764 var srChild = document.createElement('a');
765 srChild.setAttribute('id','Item'+e+'_c'+c);
766 setKeyActions(srChild,'return searchResults.NavChild(event,'+e+','+c+')');
767 setClassAttr(srChild,'SRScope');
768 srChild.setAttribute('href',searchData[e][1][c+1][0]);
769 srChild.setAttribute('onclick','parent.searchBox.CloseResultsWindow()');
770 if (searchData[e][1][c+1][1])
772 srChild.setAttribute('target','_parent');
776 srChild.setAttribute('target','_blank');
778 srChild.innerHTML = searchData[e][1][c+1][2];
779 srChildren.appendChild(srChild);
781 srEntry.appendChild(srChildren);
783 srResult.appendChild(srEntry);
784 results.appendChild(srResult);
788 function init_search()
790 var results = document.getElementById("MSearchSelectWindow");
791 for (var key in indexSectionLabels)
793 var link = document.createElement('a');
794 link.setAttribute('class','SelectItem');
795 link.setAttribute('onclick','searchBox.OnSelectItem('+key+')');
796 link.href='javascript:void(0)';
797 link.innerHTML='<span class="SelectionMark"> </span>'+indexSectionLabels[key];
798 results.appendChild(link);
800 searchBox.OnSelectItem(0);