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