1 // This file is part of Natural Docs, which is Copyright (C) 2003-2008 Greg Valure
\r
2 // Natural Docs is licensed under the GPL
\r
7 // ____________________________________________________________________________
\r
9 var agt=navigator.userAgent.toLowerCase();
\r
13 if (agt.indexOf("opera") != -1)
\r
15 browserType = "Opera";
\r
17 if (agt.indexOf("opera 7") != -1 || agt.indexOf("opera/7") != -1)
\r
18 { browserVer = "Opera7"; }
\r
19 else if (agt.indexOf("opera 8") != -1 || agt.indexOf("opera/8") != -1)
\r
20 { browserVer = "Opera8"; }
\r
21 else if (agt.indexOf("opera 9") != -1 || agt.indexOf("opera/9") != -1)
\r
22 { browserVer = "Opera9"; }
\r
25 else if (agt.indexOf("applewebkit") != -1)
\r
27 browserType = "Safari";
\r
29 if (agt.indexOf("version/3") != -1)
\r
30 { browserVer = "Safari3"; }
\r
31 else if (agt.indexOf("safari/4") != -1)
\r
32 { browserVer = "Safari2"; }
\r
35 else if (agt.indexOf("khtml") != -1)
\r
37 browserType = "Konqueror";
\r
40 else if (agt.indexOf("msie") != -1)
\r
44 if (agt.indexOf("msie 6") != -1)
\r
45 { browserVer = "IE6"; }
\r
46 else if (agt.indexOf("msie 7") != -1)
\r
47 { browserVer = "IE7"; }
\r
50 else if (agt.indexOf("gecko") != -1)
\r
52 browserType = "Firefox";
\r
54 if (agt.indexOf("rv:1.7") != -1)
\r
55 { browserVer = "Firefox1"; }
\r
56 else if (agt.indexOf("rv:1.8)") != -1 || agt.indexOf("rv:1.8.0") != -1)
\r
57 { browserVer = "Firefox15"; }
\r
58 else if (agt.indexOf("rv:1.8.1") != -1)
\r
59 { browserVer = "Firefox2"; }
\r
64 // Support Functions
\r
65 // ____________________________________________________________________________
\r
68 function GetXPosition(item)
\r
72 if (item.offsetWidth != null)
\r
74 while (item != document.body && item != null)
\r
76 position += item.offsetLeft;
\r
77 item = item.offsetParent;
\r
85 function GetYPosition(item)
\r
89 if (item.offsetWidth != null)
\r
91 while (item != document.body && item != null)
\r
93 position += item.offsetTop;
\r
94 item = item.offsetParent;
\r
102 function MoveToPosition(item, x, y)
\r
104 // Opera 5 chokes on the px extension, so it can use the Microsoft one instead.
\r
106 if (item.style.left != null)
\r
108 item.style.left = x + "px";
\r
109 item.style.top = y + "px";
\r
111 else if (item.style.pixelLeft != null)
\r
113 item.style.pixelLeft = x;
\r
114 item.style.pixelTop = y;
\r
121 // ____________________________________________________________________________
\r
124 function ToggleMenu(id)
\r
126 if (!window.document.getElementById)
\r
129 var display = window.document.getElementById(id).style.display;
\r
131 if (display == "none")
\r
132 { display = "block"; }
\r
134 { display = "none"; }
\r
136 window.document.getElementById(id).style.display = display;
\r
139 function HideAllBut(ids, max)
\r
141 if (document.getElementById)
\r
143 ids.sort( function(a,b) { return a - b; } );
\r
146 while (number < max)
\r
148 if (ids.length > 0 && number == ids[0])
\r
152 document.getElementById("MGroupContent" + number).style.display = "none";
\r
163 // ____________________________________________________________________________
\r
166 var tooltipTimer = 0;
\r
168 function ShowTip(event, tooltipID, linkID)
\r
171 { clearTimeout(tooltipTimer); };
\r
173 var docX = event.clientX + window.pageXOffset;
\r
174 var docY = event.clientY + window.pageYOffset;
\r
176 var showCommand = "ReallyShowTip('" + tooltipID + "', '" + linkID + "', " + docX + ", " + docY + ")";
\r
178 tooltipTimer = setTimeout(showCommand, 1000);
\r
181 function ReallyShowTip(tooltipID, linkID, docX, docY)
\r
188 if (document.getElementById)
\r
190 tooltip = document.getElementById(tooltipID);
\r
191 link = document.getElementById(linkID);
\r
193 /* else if (document.all)
\r
195 tooltip = eval("document.all['" + tooltipID + "']");
\r
196 link = eval("document.all['" + linkID + "']");
\r
201 var left = GetXPosition(link);
\r
202 var top = GetYPosition(link);
\r
203 top += link.offsetHeight;
\r
206 // The fallback method is to use the mouse X and Y relative to the document. We use a separate if and test if its a number
\r
207 // in case some browser snuck through the above if statement but didn't support everything.
\r
209 if (!isFinite(top) || top == 0)
\r
215 // Some spacing to get it out from under the cursor.
\r
219 // Make sure the tooltip doesnt get smushed by being too close to the edge, or in some browsers, go off the edge of the
\r
220 // page. We do it here because Konqueror does get offsetWidth right even if it doesnt get the positioning right.
\r
222 if (tooltip.offsetWidth != null)
\r
224 var width = tooltip.offsetWidth;
\r
225 var docWidth = document.body.clientWidth;
\r
227 if (left + width > docWidth)
\r
228 { left = docWidth - width - 1; }
\r
230 // If there's a horizontal scroll bar we could go past zero because it's using the page width, not the window width.
\r
235 MoveToPosition(tooltip, left, top);
\r
236 tooltip.style.visibility = "visible";
\r
240 function HideTip(tooltipID)
\r
244 clearTimeout(tooltipTimer);
\r
250 if (document.getElementById)
\r
251 { tooltip = document.getElementById(tooltipID); }
\r
252 else if (document.all)
\r
253 { tooltip = eval("document.all['" + tooltipID + "']"); }
\r
256 { tooltip.style.visibility = "hidden"; }
\r
261 // Blockquote fix for IE
\r
262 // ____________________________________________________________________________
\r
265 function NDOnLoad()
\r
267 if (browserVer == "IE6")
\r
269 var scrollboxes = document.getElementsByTagName('blockquote');
\r
271 if (scrollboxes.item(0))
\r
274 window.onresize=NDOnResize;
\r
280 var resizeTimer = 0;
\r
282 function NDOnResize()
\r
284 if (resizeTimer != 0)
\r
285 { clearTimeout(resizeTimer); };
\r
287 resizeTimer = setTimeout(NDDoResize, 250);
\r
291 function NDDoResize()
\r
293 var scrollboxes = document.getElementsByTagName('blockquote');
\r
299 while (item = scrollboxes.item(i))
\r
301 item.style.width = 100;
\r
306 while (item = scrollboxes.item(i))
\r
308 item.style.width = item.parentNode.offsetWidth;
\r
312 clearTimeout(resizeTimer);
\r
318 /* ________________________________________________________________________________________________________
\r
321 ________________________________________________________________________________________________________
\r
323 A class handling everything associated with the search panel.
\r
327 name - The name of the global variable that will be storing this instance. Is needed to be able to set timeouts.
\r
328 mode - The mode the search is going to work in. Pass <NaturalDocs::Builder::Base->CommandLineOption()>, so the
\r
329 value will be something like "HTML" or "FramedHTML".
\r
331 ________________________________________________________________________________________________________
\r
335 function SearchPanel(name, mode, resultsPath)
\r
337 if (!name || !mode || !resultsPath)
\r
338 { alert("Incorrect parameters to SearchPanel."); };
\r
341 // Group: Variables
\r
342 // ________________________________________________________________________
\r
346 The name of the global variable that will be storing this instance of the class.
\r
352 The mode the search is going to work in, such as "HTML" or "FramedHTML".
\r
358 The relative path from the current HTML page to the results page directory.
\r
360 this.resultsPath = resultsPath;
\r
364 The timeout used between a keystroke and when a search is performed.
\r
366 this.keyTimeout = 0;
\r
369 var: keyTimeoutLength
\r
370 The length of <keyTimeout> in thousandths of a second.
\r
372 this.keyTimeoutLength = 500;
\r
375 var: lastSearchValue
\r
376 The last search string executed, or an empty string if none.
\r
378 this.lastSearchValue = "";
\r
381 var: lastResultsPage
\r
382 The last results page. The value is only relevant if <lastSearchValue> is set.
\r
384 this.lastResultsPage = "";
\r
387 var: deactivateTimeout
\r
389 The timeout used between when a control is deactivated and when the entire panel is deactivated. Is necessary
\r
390 because a control may be deactivated in favor of another control in the same panel, in which case it should stay
\r
393 this.deactivateTimout = 0;
\r
396 var: deactivateTimeoutLength
\r
397 The length of <deactivateTimeout> in thousandths of a second.
\r
399 this.deactivateTimeoutLength = 200;
\r
404 // Group: DOM Elements
\r
405 // ________________________________________________________________________
\r
408 // Function: DOMSearchField
\r
409 this.DOMSearchField = function()
\r
410 { return document.getElementById("MSearchField"); };
\r
412 // Function: DOMSearchType
\r
413 this.DOMSearchType = function()
\r
414 { return document.getElementById("MSearchType"); };
\r
416 // Function: DOMPopupSearchResults
\r
417 this.DOMPopupSearchResults = function()
\r
418 { return document.getElementById("MSearchResults"); };
\r
420 // Function: DOMPopupSearchResultsWindow
\r
421 this.DOMPopupSearchResultsWindow = function()
\r
422 { return document.getElementById("MSearchResultsWindow"); };
\r
424 // Function: DOMSearchPanel
\r
425 this.DOMSearchPanel = function()
\r
426 { return document.getElementById("MSearchPanel"); };
\r
431 // Group: Event Handlers
\r
432 // ________________________________________________________________________
\r
436 Function: OnSearchFieldFocus
\r
437 Called when focus is added or removed from the search field.
\r
439 this.OnSearchFieldFocus = function(isActive)
\r
441 this.Activate(isActive);
\r
446 Function: OnSearchFieldChange
\r
447 Called when the content of the search field is changed.
\r
449 this.OnSearchFieldChange = function()
\r
451 if (this.keyTimeout)
\r
453 clearTimeout(this.keyTimeout);
\r
454 this.keyTimeout = 0;
\r
457 var searchValue = this.DOMSearchField().value.replace(/ +/g, "");
\r
459 if (searchValue != this.lastSearchValue)
\r
461 if (searchValue != "")
\r
463 this.keyTimeout = setTimeout(this.name + ".Search()", this.keyTimeoutLength);
\r
467 if (this.mode == "HTML")
\r
468 { this.DOMPopupSearchResultsWindow().style.display = "none"; };
\r
469 this.lastSearchValue = "";
\r
476 Function: OnSearchTypeFocus
\r
477 Called when focus is added or removed from the search type.
\r
479 this.OnSearchTypeFocus = function(isActive)
\r
481 this.Activate(isActive);
\r
486 Function: OnSearchTypeChange
\r
487 Called when the search type is changed.
\r
489 this.OnSearchTypeChange = function()
\r
491 var searchValue = this.DOMSearchField().value.replace(/ +/g, "");
\r
493 if (searchValue != "")
\r
501 // Group: Action Functions
\r
502 // ________________________________________________________________________
\r
506 Function: CloseResultsWindow
\r
507 Closes the results window.
\r
509 this.CloseResultsWindow = function()
\r
511 this.DOMPopupSearchResultsWindow().style.display = "none";
\r
512 this.Activate(false, true);
\r
520 this.Search = function()
\r
522 this.keyTimeout = 0;
\r
524 var searchValue = this.DOMSearchField().value.replace(/^ +/, "");
\r
525 var searchTopic = this.DOMSearchType().value;
\r
527 var pageExtension = searchValue.substr(0,1);
\r
529 if (pageExtension.match(/^[a-z]/i))
\r
530 { pageExtension = pageExtension.toUpperCase(); }
\r
531 else if (pageExtension.match(/^[0-9]/))
\r
532 { pageExtension = 'Numbers'; }
\r
534 { pageExtension = "Symbols"; };
\r
537 var resultsPageWithSearch;
\r
538 var hasResultsPage;
\r
540 // indexSectionsWithContent is defined in searchdata.js
\r
541 if (indexSectionsWithContent[searchTopic][pageExtension] == true)
\r
543 resultsPage = this.resultsPath + '/' + searchTopic + pageExtension + '.html';
\r
544 resultsPageWithSearch = resultsPage+'?'+escape(searchValue);
\r
545 hasResultsPage = true;
\r
549 resultsPage = this.resultsPath + '/NoResults.html';
\r
550 resultsPageWithSearch = resultsPage;
\r
551 hasResultsPage = false;
\r
555 if (this.mode == "HTML")
\r
556 { resultsFrame = window.frames.MSearchResults; }
\r
557 else if (this.mode == "FramedHTML")
\r
558 { resultsFrame = window.top.frames['Content']; };
\r
561 if (resultsPage != this.lastResultsPage ||
\r
563 // Bug in IE. If everything becomes hidden in a run, none of them will be able to be reshown in the next for some
\r
564 // reason. It counts the right number of results, and you can even read the display as "block" after setting it, but it
\r
565 // just doesn't work in IE 6 or IE 7. So if we're on the right page but the previous search had no results, reload the
\r
566 // page anyway to get around the bug.
\r
567 (browserType == "IE" && hasResultsPage &&
\r
568 (!resultsFrame.searchResults || resultsFrame.searchResults.lastMatchCount == 0)) )
\r
571 resultsFrame.location.href = resultsPageWithSearch;
\r
574 // So if the results page is right and there's no IE bug, reperform the search on the existing page. We have to check if there
\r
575 // are results because NoResults.html doesn't have any JavaScript, and it would be useless to do anything on that page even
\r
577 else if (hasResultsPage)
\r
579 // We need to check if this exists in case the frame is present but didn't finish loading.
\r
580 if (resultsFrame.searchResults)
\r
581 { resultsFrame.searchResults.Search(searchValue); }
\r
583 // Otherwise just reload instead of waiting.
\r
585 { resultsFrame.location.href = resultsPageWithSearch; };
\r
589 var domPopupSearchResultsWindow = this.DOMPopupSearchResultsWindow();
\r
591 if (this.mode == "HTML" && domPopupSearchResultsWindow.style.display != "block")
\r
593 var domSearchType = this.DOMSearchType();
\r
595 var left = GetXPosition(domSearchType);
\r
596 var top = GetYPosition(domSearchType) + domSearchType.offsetHeight;
\r
598 MoveToPosition(domPopupSearchResultsWindow, left, top);
\r
599 domPopupSearchResultsWindow.style.display = 'block';
\r
603 this.lastSearchValue = searchValue;
\r
604 this.lastResultsPage = resultsPage;
\r
609 // Group: Activation Functions
\r
610 // Functions that handle whether the entire panel is active or not.
\r
611 // ________________________________________________________________________
\r
617 Activates or deactivates the search panel, resetting things to their default values if necessary. You can call this on every
\r
618 control's OnBlur() and it will handle not deactivating the entire panel when focus is just switching between them transparently.
\r
622 isActive - Whether you're activating or deactivating the panel.
\r
623 ignoreDeactivateDelay - Set if you're positive the action will deactivate the panel and thus want to skip the delay.
\r
625 this.Activate = function(isActive, ignoreDeactivateDelay)
\r
627 // We want to ignore isActive being false while the results window is open.
\r
628 if (isActive || (this.mode == "HTML" && this.DOMPopupSearchResultsWindow().style.display == "block"))
\r
630 if (this.inactivateTimeout)
\r
632 clearTimeout(this.inactivateTimeout);
\r
633 this.inactivateTimeout = 0;
\r
636 this.DOMSearchPanel().className = 'MSearchPanelActive';
\r
638 var searchField = this.DOMSearchField();
\r
640 if (searchField.value == 'Search')
\r
641 { searchField.value = ""; }
\r
643 else if (!ignoreDeactivateDelay)
\r
645 this.inactivateTimeout = setTimeout(this.name + ".InactivateAfterTimeout()", this.inactivateTimeoutLength);
\r
649 this.InactivateAfterTimeout();
\r
655 Function: InactivateAfterTimeout
\r
657 Called by <inactivateTimeout>, which is set by <Activate()>. Inactivation occurs on a timeout because a control may
\r
658 receive OnBlur() when focus is really transferring to another control in the search panel. In this case we don't want to
\r
659 actually deactivate the panel because not only would that cause a visible flicker but it could also reset the search value.
\r
660 So by doing it on a timeout instead, there's a short period where the second control's OnFocus() can cancel the deactivation.
\r
662 this.InactivateAfterTimeout = function()
\r
664 this.inactivateTimeout = 0;
\r
666 this.DOMSearchPanel().className = 'MSearchPanelInactive';
\r
667 this.DOMSearchField().value = "Search";
\r
669 this.lastSearchValue = "";
\r
670 this.lastResultsPage = "";
\r
677 /* ________________________________________________________________________________________________________
\r
679 Class: SearchResults
\r
680 _________________________________________________________________________________________________________
\r
682 The class that handles everything on the search results page.
\r
683 _________________________________________________________________________________________________________
\r
687 function SearchResults(name, mode)
\r
691 The mode the search is going to work in, such as "HTML" or "FramedHTML".
\r
696 var: lastMatchCount
\r
697 The number of matches from the last run of <Search()>.
\r
699 this.lastMatchCount = 0;
\r
704 Toggles the visibility of the passed element ID.
\r
706 this.Toggle = function(id)
\r
708 if (this.mode == "FramedHTML")
\r
711 var parentElement = document.getElementById(id);
\r
713 var element = parentElement.firstChild;
\r
715 while (element && element != parentElement)
\r
717 if (element.nodeName == 'DIV' && element.className == 'ISubIndex')
\r
719 if (element.style.display == 'block')
\r
720 { element.style.display = "none"; }
\r
722 { element.style.display = 'block'; }
\r
725 if (element.nodeName == 'DIV' && element.hasChildNodes())
\r
726 { element = element.firstChild; }
\r
727 else if (element.nextSibling)
\r
728 { element = element.nextSibling; }
\r
733 element = element.parentNode;
\r
735 while (element && element != parentElement && !element.nextSibling);
\r
737 if (element && element != parentElement)
\r
738 { element = element.nextSibling; };
\r
747 Searches for the passed string. If there is no parameter, it takes it from the URL query.
\r
749 Always returns true, since other documents may try to call it and that may or may not be possible.
\r
751 this.Search = function(search)
\r
755 search = window.location.search;
\r
756 search = search.substring(1); // Remove the leading ?
\r
757 search = unescape(search);
\r
760 search = search.replace(/^ +/, "");
\r
761 search = search.replace(/ +$/, "");
\r
762 search = search.toLowerCase();
\r
764 if (search.match(/[^a-z0-9]/)) // Just a little speedup so it doesn't have to go through the below unnecessarily.
\r
766 search = search.replace(/\_/g, "_und");
\r
767 search = search.replace(/\ +/gi, "_spc");
\r
768 search = search.replace(/\~/g, "_til");
\r
769 search = search.replace(/\!/g, "_exc");
\r
770 search = search.replace(/\@/g, "_att");
\r
771 search = search.replace(/\#/g, "_num");
\r
772 search = search.replace(/\$/g, "_dol");
\r
773 search = search.replace(/\%/g, "_pct");
\r
774 search = search.replace(/\^/g, "_car");
\r
775 search = search.replace(/\&/g, "_amp");
\r
776 search = search.replace(/\*/g, "_ast");
\r
777 search = search.replace(/\(/g, "_lpa");
\r
778 search = search.replace(/\)/g, "_rpa");
\r
779 search = search.replace(/\-/g, "_min");
\r
780 search = search.replace(/\+/g, "_plu");
\r
781 search = search.replace(/\=/g, "_equ");
\r
782 search = search.replace(/\{/g, "_lbc");
\r
783 search = search.replace(/\}/g, "_rbc");
\r
784 search = search.replace(/\[/g, "_lbk");
\r
785 search = search.replace(/\]/g, "_rbk");
\r
786 search = search.replace(/\:/g, "_col");
\r
787 search = search.replace(/\;/g, "_sco");
\r
788 search = search.replace(/\"/g, "_quo");
\r
789 search = search.replace(/\'/g, "_apo");
\r
790 search = search.replace(/\</g, "_lan");
\r
791 search = search.replace(/\>/g, "_ran");
\r
792 search = search.replace(/\,/g, "_com");
\r
793 search = search.replace(/\./g, "_per");
\r
794 search = search.replace(/\?/g, "_que");
\r
795 search = search.replace(/\//g, "_sla");
\r
796 search = search.replace(/[^a-z0-9\_]i/gi, "_zzz");
\r
799 var resultRows = document.getElementsByTagName("div");
\r
803 while (i < resultRows.length)
\r
805 var row = resultRows.item(i);
\r
807 if (row.className == "SRResult")
\r
809 var rowMatchName = row.id.toLowerCase();
\r
810 rowMatchName = rowMatchName.replace(/^sr\d*_/, '');
\r
812 if (search.length <= rowMatchName.length && rowMatchName.substr(0, search.length) == search)
\r
814 row.style.display = "block";
\r
818 { row.style.display = "none"; };
\r
824 document.getElementById("Searching").style.display="none";
\r
827 { document.getElementById("NoMatches").style.display="block"; }
\r
829 { document.getElementById("NoMatches").style.display="none"; }
\r
831 this.lastMatchCount = matches;
\r