docs.js revision 0e76e7e4dd44d9e8893cbf2e077a96e655628e99
1var classesNav;
2var devdocNav;
3var sidenav;
4var cookie_namespace = 'android_developer';
5var NAV_PREF_TREE = "tree";
6var NAV_PREF_PANELS = "panels";
7var nav_pref;
8var isMobile = false; // true if mobile, so we can adjust some layout
9var mPagePath; // initialized in ready() function
10
11var basePath = getBaseUri(location.pathname);
12var SITE_ROOT = toRoot + basePath.substring(1,basePath.indexOf("/",1));
13var GOOGLE_DATA; // combined data for google service apis, used for search suggest
14
15// Ensure that all ajax getScript() requests allow caching
16$.ajaxSetup({
17  cache: true
18});
19
20/******  ON LOAD SET UP STUFF *********/
21
22var navBarIsFixed = false;
23$(document).ready(function() {
24
25  // load json file for JD doc search suggestions
26  $.getScript(toRoot + 'reference/jd_lists.js');
27  // load json file for Android API search suggestions
28  $.getScript(toRoot + 'reference/lists.js');
29  // load json files for Google services API suggestions
30  $.getScript(toRoot + 'reference/gcm_lists.js', function(data, textStatus, jqxhr) {
31      // once the GCM json (GCM_DATA) is loaded, load the GMS json (GMS_DATA) and merge the data
32      if(jqxhr.status === 200) {
33          $.getScript(toRoot + 'reference/gms_lists.js', function(data, textStatus, jqxhr) {
34              if(jqxhr.status === 200) {
35                  // combine GCM and GMS data
36                  GOOGLE_DATA = GMS_DATA;
37                  var start = GOOGLE_DATA.length;
38                  for (var i=0; i<GCM_DATA.length; i++) {
39                      GOOGLE_DATA.push({id:start+i, label:GCM_DATA[i].label,
40                              link:GCM_DATA[i].link, type:GCM_DATA[i].type});
41                  }
42              }
43          });
44      }
45  });
46
47  // setup keyboard listener for search shortcut
48  $('body').keyup(function(event) {
49    if (event.which == 191) {
50      $('#search_autocomplete').focus();
51    }
52  });
53
54  // init the fullscreen toggle click event
55  $('#nav-swap .fullscreen').click(function(){
56    if ($(this).hasClass('disabled')) {
57      toggleFullscreen(true);
58    } else {
59      toggleFullscreen(false);
60    }
61  });
62
63  // initialize the divs with custom scrollbars
64  $('.scroll-pane').jScrollPane( {verticalGutter:0} );
65
66  // add HRs below all H2s (except for a few other h2 variants)
67  $('h2').not('#qv h2').not('#tb h2').not('.sidebox h2').not('#devdoc-nav h2').not('h2.norule').css({marginBottom:0}).after('<hr/>');
68
69  // set up the search close button
70  $('.search .close').click(function() {
71    $searchInput = $('#search_autocomplete');
72    $searchInput.attr('value', '');
73    $(this).addClass("hide");
74    $("#search-container").removeClass('active');
75    $("#search_autocomplete").blur();
76    search_focus_changed($searchInput.get(), false);
77    hideResults();
78  });
79
80  // Set up quicknav
81  var quicknav_open = false;
82  $("#btn-quicknav").click(function() {
83    if (quicknav_open) {
84      $(this).removeClass('active');
85      quicknav_open = false;
86      collapse();
87    } else {
88      $(this).addClass('active');
89      quicknav_open = true;
90      expand();
91    }
92  })
93
94  var expand = function() {
95   $('#header-wrap').addClass('quicknav');
96   $('#quicknav').stop().show().animate({opacity:'1'});
97  }
98
99  var collapse = function() {
100    $('#quicknav').stop().animate({opacity:'0'}, 100, function() {
101      $(this).hide();
102      $('#header-wrap').removeClass('quicknav');
103    });
104  }
105
106
107  //Set up search
108  $("#search_autocomplete").focus(function() {
109    $("#search-container").addClass('active');
110  })
111  $("#search-container").mouseover(function() {
112    $("#search-container").addClass('active');
113    $("#search_autocomplete").focus();
114  })
115  $("#search-container").mouseout(function() {
116    if ($("#search_autocomplete").is(":focus")) return;
117    if ($("#search_autocomplete").val() == '') {
118      setTimeout(function(){
119        $("#search-container").removeClass('active');
120        $("#search_autocomplete").blur();
121      },250);
122    }
123  })
124  $("#search_autocomplete").blur(function() {
125    if ($("#search_autocomplete").val() == '') {
126      $("#search-container").removeClass('active');
127    }
128  })
129
130
131  // prep nav expandos
132  var pagePath = document.location.pathname;
133  // account for intl docs by removing the intl/*/ path
134  if (pagePath.indexOf("/intl/") == 0) {
135    pagePath = pagePath.substr(pagePath.indexOf("/",6)); // start after intl/ to get last /
136  }
137
138  if (pagePath.indexOf(SITE_ROOT) == 0) {
139    if (pagePath == '' || pagePath.charAt(pagePath.length - 1) == '/') {
140      pagePath += 'index.html';
141    }
142  }
143
144  // Need a copy of the pagePath before it gets changed in the next block;
145  // it's needed to perform proper tab highlighting in offline docs (see rootDir below)
146  var pagePathOriginal = pagePath;
147  if (SITE_ROOT.match(/\.\.\//) || SITE_ROOT == '') {
148    // If running locally, SITE_ROOT will be a relative path, so account for that by
149    // finding the relative URL to this page. This will allow us to find links on the page
150    // leading back to this page.
151    var pathParts = pagePath.split('/');
152    var relativePagePathParts = [];
153    var upDirs = (SITE_ROOT.match(/(\.\.\/)+/) || [''])[0].length / 3;
154    for (var i = 0; i < upDirs; i++) {
155      relativePagePathParts.push('..');
156    }
157    for (var i = 0; i < upDirs; i++) {
158      relativePagePathParts.push(pathParts[pathParts.length - (upDirs - i) - 1]);
159    }
160    relativePagePathParts.push(pathParts[pathParts.length - 1]);
161    pagePath = relativePagePathParts.join('/');
162  } else {
163    // Otherwise the page path is already an absolute URL
164  }
165
166  // Highlight the header tabs...
167  // highlight Design tab
168  if ($("body").hasClass("design")) {
169    $("#header li.design a").addClass("selected");
170
171  // highlight Develop tab
172  } else if ($("body").hasClass("develop") || $("body").hasClass("google")) {
173    $("#header li.develop a").addClass("selected");
174    // In Develop docs, also highlight appropriate sub-tab
175    var rootDir = pagePathOriginal.substring(1,pagePathOriginal.indexOf('/', 1));
176    if (rootDir == "training") {
177      $("#nav-x li.training a").addClass("selected");
178    } else if (rootDir == "guide") {
179      $("#nav-x li.guide a").addClass("selected");
180    } else if (rootDir == "reference") {
181      // If the root is reference, but page is also part of Google Services, select Google
182      if ($("body").hasClass("google")) {
183        $("#nav-x li.google a").addClass("selected");
184      } else {
185        $("#nav-x li.reference a").addClass("selected");
186      }
187    } else if ((rootDir == "tools") || (rootDir == "sdk")) {
188      $("#nav-x li.tools a").addClass("selected");
189    } else if ($("body").hasClass("google")) {
190      $("#nav-x li.google a").addClass("selected");
191    }
192
193  // highlight Distribute tab
194  } else if ($("body").hasClass("distribute")) {
195    $("#header li.distribute a").addClass("selected");
196  }
197
198  // set global variable so we can highlight the sidenav a bit later (such as for google reference)
199  // and highlight the sidenav
200  mPagePath = pagePath;
201  highlightSidenav();
202
203  // set up prev/next links if they exist
204  var $selNavLink = $('#nav').find('a[href="' + pagePath + '"]');
205  var $selListItem;
206  if ($selNavLink.length) {
207    $selListItem = $selNavLink.closest('li');
208
209    // set up prev links
210    var $prevLink = [];
211    var $prevListItem = $selListItem.prev('li');
212
213    var crossBoundaries = ($("body.design").length > 0) || ($("body.guide").length > 0) ? true :
214false; // navigate across topic boundaries only in design docs
215    if ($prevListItem.length) {
216      if ($prevListItem.hasClass('nav-section')) {
217        // jump to last topic of previous section
218        $prevLink = $prevListItem.find('a:last');
219      } else if (!$selListItem.hasClass('nav-section')) {
220        // jump to previous topic in this section
221        $prevLink = $prevListItem.find('a:eq(0)');
222      }
223    } else {
224      // jump to this section's index page (if it exists)
225      var $parentListItem = $selListItem.parents('li');
226      $prevLink = $selListItem.parents('li').find('a');
227
228      // except if cross boundaries aren't allowed, and we're at the top of a section already
229      // (and there's another parent)
230      if (!crossBoundaries && $parentListItem.hasClass('nav-section')
231                           && $selListItem.hasClass('nav-section')) {
232        $prevLink = [];
233      }
234    }
235
236    // set up next links
237    var $nextLink = [];
238    var startClass = false;
239    var training = $(".next-class-link").length; // decides whether to provide "next class" link
240    var isCrossingBoundary = false;
241
242    if ($selListItem.hasClass('nav-section')) {
243      // we're on an index page, jump to the first topic
244      $nextLink = $selListItem.find('ul:eq(0)').find('a:eq(0)');
245
246      // if there aren't any children, go to the next section (required for About pages)
247      if($nextLink.length == 0) {
248        $nextLink = $selListItem.next('li').find('a');
249      } else if ($('.topic-start-link').length) {
250        // as long as there's a child link and there is a "topic start link" (we're on a landing)
251        // then set the landing page "start link" text to be the first doc title
252        $('.topic-start-link').text($nextLink.text().toUpperCase());
253      }
254
255      // If the selected page has a description, then it's a class or article homepage
256      if ($selListItem.find('a[description]').length) {
257        // this means we're on a class landing page
258        startClass = true;
259      }
260    } else {
261      // jump to the next topic in this section (if it exists)
262      $nextLink = $selListItem.next('li').find('a:eq(0)');
263      if (!$nextLink.length) {
264        isCrossingBoundary = true;
265        // no more topics in this section, jump to the first topic in the next section
266        $nextLink = $selListItem.parents('li:eq(0)').next('li.nav-section').find('a:eq(0)');
267        if (!$nextLink.length) {  // Go up another layer to look for next page (lesson > class > course)
268          $nextLink = $selListItem.parents('li:eq(1)').next('li.nav-section').find('a:eq(0)');
269        }
270      }
271    }
272
273    if (startClass) {
274      $('.start-class-link').attr('href', $nextLink.attr('href')).removeClass("hide");
275
276      // if there's no training bar (below the start button),
277      // then we need to add a bottom border to button
278      if (!$("#tb").length) {
279        $('.start-class-link').css({'border-bottom':'1px solid #DADADA'});
280      }
281    } else if (isCrossingBoundary && !$('body.design').length) {  // Design always crosses boundaries
282      $('.content-footer.next-class').show();
283      $('.next-page-link').attr('href','')
284                          .removeClass("hide").addClass("disabled")
285                          .click(function() { return false; });
286
287      $('.next-class-link').attr('href',$nextLink.attr('href'))
288                          .removeClass("hide").append($nextLink.html());
289      $('.next-class-link').find('.new').empty();
290    } else {
291      $('.next-page-link').attr('href', $nextLink.attr('href')).removeClass("hide");
292    }
293
294    if (!startClass && $prevLink.length) {
295      var prevHref = $prevLink.attr('href');
296      if (prevHref == SITE_ROOT + 'index.html') {
297        // Don't show Previous when it leads to the homepage
298      } else {
299        $('.prev-page-link').attr('href', $prevLink.attr('href')).removeClass("hide");
300      }
301    }
302
303    // If this is a training 'article', there should be no prev/next nav
304    // ... if the grandparent is the "nav" ... and it has no child list items...
305    if (training && $selListItem.parents('ul').eq(1).is('[id="nav"]') &&
306        !$selListItem.find('li').length) {
307      $('.next-page-link,.prev-page-link').attr('href','').addClass("disabled")
308                          .click(function() { return false; });
309    }
310
311  }
312
313
314
315  // Set up the course landing pages for Training with class names and descriptions
316  if ($('body.trainingcourse').length) {
317    var $classLinks = $selListItem.find('ul li a').not('#nav .nav-section .nav-section ul a');
318    var $classDescriptions = $classLinks.attr('description');
319
320    var $olClasses  = $('<ol class="class-list"></ol>');
321    var $liClass;
322    var $imgIcon;
323    var $h2Title;
324    var $pSummary;
325    var $olLessons;
326    var $liLesson;
327    $classLinks.each(function(index) {
328      $liClass  = $('<li></li>');
329      $h2Title  = $('<a class="title" href="'+$(this).attr('href')+'"><h2>' + $(this).html()+'</h2><span></span></a>');
330      $pSummary = $('<p class="description">' + $(this).attr('description') + '</p>');
331
332      $olLessons  = $('<ol class="lesson-list"></ol>');
333
334      $lessons = $(this).closest('li').find('ul li a');
335
336      if ($lessons.length) {
337        $imgIcon = $('<img src="'+toRoot+'assets/images/resource-tutorial.png" alt=""/>');
338        $lessons.each(function(index) {
339          $olLessons.append('<li><a href="'+$(this).attr('href')+'">' + $(this).html()+'</a></li>');
340        });
341      } else {
342        $imgIcon = $('<img src="'+toRoot+'assets/images/resource-article.png" alt=""/>');
343        $pSummary.addClass('article');
344      }
345
346      $liClass.append($h2Title).append($imgIcon).append($pSummary).append($olLessons);
347      $olClasses.append($liClass);
348    });
349    $('.jd-descr').append($olClasses);
350  }
351
352
353
354
355  // Set up expand/collapse behavior
356  $('#nav li.nav-section .nav-section-header').click(function() {
357    var section = $(this).closest('li.nav-section');
358    if (section.hasClass('expanded')) {
359    /* hide me */
360    //  if (section.hasClass('selected') || section.find('li').hasClass('selected')) {
361   //   /* but not if myself or my descendents are selected */
362   //     return;
363    //  }
364      section.children('ul').slideUp(250, function() {
365        section.closest('li').removeClass('expanded');
366        resizeNav();
367      });
368    } else {
369    /* show me */
370      // first hide all other siblings
371      var $others = $('li.nav-section.expanded', $(this).closest('ul'));
372      $others.removeClass('expanded').children('ul').slideUp(250);
373
374      // now expand me
375      section.closest('li').addClass('expanded');
376      section.children('ul').slideDown(250, function() {
377        resizeNav();
378      });
379    }
380  });
381
382  $(".scroll-pane").scroll(function(event) {
383      event.preventDefault();
384      return false;
385  });
386
387  /* Resize nav height when window height changes */
388  $(window).resize(function() {
389    if ($('#side-nav').length == 0) return;
390    var stylesheet = $('link[rel="stylesheet"][class="fullscreen"]');
391    setNavBarLeftPos(); // do this even if sidenav isn't fixed because it could become fixed
392    // make sidenav behave when resizing the window and side-scolling is a concern
393    if (navBarIsFixed) {
394      if ((stylesheet.attr("disabled") == "disabled") || stylesheet.length == 0) {
395        updateSideNavPosition();
396      } else {
397        updateSidenavFullscreenWidth();
398      }
399    }
400    resizeNav();
401  });
402
403
404  // Set up fixed navbar
405  var prevScrollLeft = 0; // used to compare current position to previous position of horiz scroll
406  $(window).scroll(function(event) {
407    if ($('#side-nav').length == 0) return;
408    if (event.target.nodeName == "DIV") {
409      // Dump scroll event if the target is a DIV, because that means the event is coming
410      // from a scrollable div and so there's no need to make adjustments to our layout
411      return;
412    }
413    var scrollTop = $(window).scrollTop();
414    var headerHeight = $('#header').outerHeight();
415    var subheaderHeight = $('#nav-x').outerHeight();
416    var searchResultHeight = $('#searchResults').is(":visible") ?
417                             $('#searchResults').outerHeight() : 0;
418    var totalHeaderHeight = headerHeight + subheaderHeight + searchResultHeight;
419    // we set the navbar fixed when the scroll position is beyond the height of the site header...
420    var navBarShouldBeFixed = scrollTop > totalHeaderHeight;
421    // ... except if the document content is shorter than the sidenav height.
422    // (this is necessary to avoid crazy behavior on OSX Lion due to overscroll bouncing)
423    if ($("#doc-col").height() < $("#side-nav").height()) {
424      navBarShouldBeFixed = false;
425    }
426
427    var scrollLeft = $(window).scrollLeft();
428    // When the sidenav is fixed and user scrolls horizontally, reposition the sidenav to match
429    if (navBarIsFixed && (scrollLeft != prevScrollLeft)) {
430      updateSideNavPosition();
431      prevScrollLeft = scrollLeft;
432    }
433
434    // Don't continue if the header is sufficently far away
435    // (to avoid intensive resizing that slows scrolling)
436    if (navBarIsFixed && navBarShouldBeFixed) {
437      return;
438    }
439
440    if (navBarIsFixed != navBarShouldBeFixed) {
441      if (navBarShouldBeFixed) {
442        // make it fixed
443        var width = $('#devdoc-nav').width();
444        $('#devdoc-nav')
445            .addClass('fixed')
446            .css({'width':width+'px'})
447            .prependTo('#body-content');
448        // add neato "back to top" button
449        $('#devdoc-nav a.totop').css({'display':'block','width':$("#nav").innerWidth()+'px'});
450
451        // update the sidenaav position for side scrolling
452        updateSideNavPosition();
453      } else {
454        // make it static again
455        $('#devdoc-nav')
456            .removeClass('fixed')
457            .css({'width':'auto','margin':''})
458            .prependTo('#side-nav');
459        $('#devdoc-nav a.totop').hide();
460      }
461      navBarIsFixed = navBarShouldBeFixed;
462    }
463
464    resizeNav(250); // pass true in order to delay the scrollbar re-initialization for performance
465  });
466
467
468  var navBarLeftPos;
469  if ($('#devdoc-nav').length) {
470    setNavBarLeftPos();
471  }
472
473
474  // Stop expand/collapse behavior when clicking on nav section links (since we're navigating away
475  // from the page)
476  $('.nav-section-header').find('a:eq(0)').click(function(evt) {
477    window.location.href = $(this).attr('href');
478    return false;
479  });
480
481  // Set up play-on-hover <video> tags.
482  $('video.play-on-hover').bind('click', function(){
483    $(this).get(0).load(); // in case the video isn't seekable
484    $(this).get(0).play();
485  });
486
487  // Set up tooltips
488  var TOOLTIP_MARGIN = 10;
489  $('acronym,.tooltip-link').each(function() {
490    var $target = $(this);
491    var $tooltip = $('<div>')
492        .addClass('tooltip-box')
493        .append($target.attr('title'))
494        .hide()
495        .appendTo('body');
496    $target.removeAttr('title');
497
498    $target.hover(function() {
499      // in
500      var targetRect = $target.offset();
501      targetRect.width = $target.width();
502      targetRect.height = $target.height();
503
504      $tooltip.css({
505        left: targetRect.left,
506        top: targetRect.top + targetRect.height + TOOLTIP_MARGIN
507      });
508      $tooltip.addClass('below');
509      $tooltip.show();
510    }, function() {
511      // out
512      $tooltip.hide();
513    });
514  });
515
516  // Set up <h2> deeplinks
517  $('h2').click(function() {
518    var id = $(this).attr('id');
519    if (id) {
520      document.location.hash = id;
521    }
522  });
523
524  //Loads the +1 button
525  var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
526  po.src = 'https://apis.google.com/js/plusone.js';
527  var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
528
529
530  // Revise the sidenav widths to make room for the scrollbar
531  // which avoids the visible width from changing each time the bar appears
532  var $sidenav = $("#side-nav");
533  var sidenav_width = parseInt($sidenav.innerWidth());
534
535  $("#devdoc-nav  #nav").css("width", sidenav_width - 4 + "px"); // 4px is scrollbar width
536
537
538  $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller
539
540  if ($(".scroll-pane").length > 1) {
541    // Check if there's a user preference for the panel heights
542    var cookieHeight = readCookie("reference_height");
543    if (cookieHeight) {
544      restoreHeight(cookieHeight);
545    }
546  }
547
548  resizeNav();
549
550  /* init the language selector based on user cookie for lang */
551  loadLangPref();
552  changeNavLang(getLangPref());
553
554  /* setup event handlers to ensure the overflow menu is visible while picking lang */
555  $("#language select")
556      .mousedown(function() {
557        $("div.morehover").addClass("hover"); })
558      .blur(function() {
559        $("div.morehover").removeClass("hover"); });
560
561  /* some global variable setup */
562  resizePackagesNav = $("#resize-packages-nav");
563  classesNav = $("#classes-nav");
564  devdocNav = $("#devdoc-nav");
565
566  var cookiePath = "";
567  if (location.href.indexOf("/reference/") != -1) {
568    cookiePath = "reference_";
569  } else if (location.href.indexOf("/guide/") != -1) {
570    cookiePath = "guide_";
571  } else if (location.href.indexOf("/tools/") != -1) {
572    cookiePath = "tools_";
573  } else if (location.href.indexOf("/training/") != -1) {
574    cookiePath = "training_";
575  } else if (location.href.indexOf("/design/") != -1) {
576    cookiePath = "design_";
577  } else if (location.href.indexOf("/distribute/") != -1) {
578    cookiePath = "distribute_";
579  }
580
581});
582// END of the onload event
583
584
585function highlightSidenav() {
586  // select current page in sidenav and header, and set up prev/next links if they exist
587  var $selNavLink = $('#nav').find('a[href="' + mPagePath + '"]');
588  var $selListItem;
589  if ($selNavLink.length) {
590
591    // Find this page's <li> in sidenav and set selected
592    $selListItem = $selNavLink.closest('li');
593    $selListItem.addClass('selected');
594
595    // Traverse up the tree and expand all parent nav-sections
596    $selNavLink.parents('li.nav-section').each(function() {
597      $(this).addClass('expanded');
598      $(this).children('ul').show();
599    });
600  }
601}
602
603
604function toggleFullscreen(enable) {
605  var delay = 20;
606  var enabled = true;
607  var stylesheet = $('link[rel="stylesheet"][class="fullscreen"]');
608  if (enable) {
609    // Currently NOT USING fullscreen; enable fullscreen
610    stylesheet.removeAttr('disabled');
611    $('#nav-swap .fullscreen').removeClass('disabled');
612    $('#devdoc-nav').css({left:''});
613    setTimeout(updateSidenavFullscreenWidth,delay); // need to wait a moment for css to switch
614    enabled = true;
615  } else {
616    // Currently USING fullscreen; disable fullscreen
617    stylesheet.attr('disabled', 'disabled');
618    $('#nav-swap .fullscreen').addClass('disabled');
619    setTimeout(updateSidenavFixedWidth,delay); // need to wait a moment for css to switch
620    enabled = false;
621  }
622  writeCookie("fullscreen", enabled, null, null);
623  setNavBarLeftPos();
624  resizeNav(delay);
625  updateSideNavPosition();
626  setTimeout(initSidenavHeightResize,delay);
627}
628
629
630function setNavBarLeftPos() {
631  navBarLeftPos = $('#body-content').offset().left;
632}
633
634
635function updateSideNavPosition() {
636  var newLeft = $(window).scrollLeft() - navBarLeftPos;
637  $('#devdoc-nav').css({left: -newLeft});
638  $('#devdoc-nav .totop').css({left: -(newLeft - parseInt($('#side-nav').css('margin-left')))});
639}
640
641
642
643
644
645
646
647
648// TODO: use $(document).ready instead
649function addLoadEvent(newfun) {
650  var current = window.onload;
651  if (typeof window.onload != 'function') {
652    window.onload = newfun;
653  } else {
654    window.onload = function() {
655      current();
656      newfun();
657    }
658  }
659}
660
661var agent = navigator['userAgent'].toLowerCase();
662// If a mobile phone, set flag and do mobile setup
663if ((agent.indexOf("mobile") != -1) ||      // android, iphone, ipod
664    (agent.indexOf("blackberry") != -1) ||
665    (agent.indexOf("webos") != -1) ||
666    (agent.indexOf("mini") != -1)) {        // opera mini browsers
667  isMobile = true;
668}
669
670
671addLoadEvent( function() {
672  $("pre:not(.no-pretty-print)").addClass("prettyprint");
673  prettyPrint();
674} );
675
676
677
678
679/* ######### RESIZE THE SIDENAV HEIGHT ########## */
680
681function resizeNav(delay) {
682  var $nav = $("#devdoc-nav");
683  var $window = $(window);
684  var navHeight;
685
686  // Get the height of entire window and the total header height.
687  // Then figure out based on scroll position whether the header is visible
688  var windowHeight = $window.height();
689  var scrollTop = $window.scrollTop();
690  var headerHeight = $('#header').outerHeight();
691  var subheaderHeight = $('#nav-x').outerHeight();
692  var headerVisible = (scrollTop < (headerHeight + subheaderHeight));
693
694  // get the height of space between nav and top of window.
695  // Could be either margin or top position, depending on whether the nav is fixed.
696  var topMargin = (parseInt($nav.css('margin-top')) || parseInt($nav.css('top'))) + 1;
697  // add 1 for the #side-nav bottom margin
698
699  // Depending on whether the header is visible, set the side nav's height.
700  if (headerVisible) {
701    // The sidenav height grows as the header goes off screen
702    navHeight = windowHeight - (headerHeight + subheaderHeight - scrollTop) - topMargin;
703  } else {
704    // Once header is off screen, the nav height is almost full window height
705    navHeight = windowHeight - topMargin;
706  }
707
708
709
710  $scrollPanes = $(".scroll-pane");
711  if ($scrollPanes.length > 1) {
712    // subtract the height of the api level widget and nav swapper from the available nav height
713    navHeight -= ($('#api-nav-header').outerHeight(true) + $('#nav-swap').outerHeight(true));
714
715    $("#swapper").css({height:navHeight + "px"});
716    if ($("#nav-tree").is(":visible")) {
717      $("#nav-tree").css({height:navHeight});
718    }
719
720    var classesHeight = navHeight - parseInt($("#resize-packages-nav").css("height")) - 10 + "px";
721    //subtract 10px to account for drag bar
722
723    // if the window becomes small enough to make the class panel height 0,
724    // then the package panel should begin to shrink
725    if (parseInt(classesHeight) <= 0) {
726      $("#resize-packages-nav").css({height:navHeight - 10}); //subtract 10px for drag bar
727      $("#packages-nav").css({height:navHeight - 10});
728    }
729
730    $("#classes-nav").css({'height':classesHeight, 'margin-top':'10px'});
731    $("#classes-nav .jspContainer").css({height:classesHeight});
732
733
734  } else {
735    $nav.height(navHeight);
736  }
737
738  if (delay) {
739    updateFromResize = true;
740    delayedReInitScrollbars(delay);
741  } else {
742    reInitScrollbars();
743  }
744
745}
746
747var updateScrollbars = false;
748var updateFromResize = false;
749
750/* Re-initialize the scrollbars to account for changed nav size.
751 * This method postpones the actual update by a 1/4 second in order to optimize the
752 * scroll performance while the header is still visible, because re-initializing the
753 * scroll panes is an intensive process.
754 */
755function delayedReInitScrollbars(delay) {
756  // If we're scheduled for an update, but have received another resize request
757  // before the scheduled resize has occured, just ignore the new request
758  // (and wait for the scheduled one).
759  if (updateScrollbars && updateFromResize) {
760    updateFromResize = false;
761    return;
762  }
763
764  // We're scheduled for an update and the update request came from this method's setTimeout
765  if (updateScrollbars && !updateFromResize) {
766    reInitScrollbars();
767    updateScrollbars = false;
768  } else {
769    updateScrollbars = true;
770    updateFromResize = false;
771    setTimeout('delayedReInitScrollbars()',delay);
772  }
773}
774
775/* Re-initialize the scrollbars to account for changed nav size. */
776function reInitScrollbars() {
777  var pane = $(".scroll-pane").each(function(){
778    var api = $(this).data('jsp');
779    if (!api) { setTimeout(reInitScrollbars,300); return;}
780    api.reinitialise( {verticalGutter:0} );
781  });
782  $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller
783}
784
785
786/* Resize the height of the nav panels in the reference,
787 * and save the new size to a cookie */
788function saveNavPanels() {
789  var basePath = getBaseUri(location.pathname);
790  var section = basePath.substring(1,basePath.indexOf("/",1));
791  writeCookie("height", resizePackagesNav.css("height"), section, null);
792}
793
794
795
796function restoreHeight(packageHeight) {
797    $("#resize-packages-nav").height(packageHeight);
798    $("#packages-nav").height(packageHeight);
799  //  var classesHeight = navHeight - packageHeight;
800 //   $("#classes-nav").css({height:classesHeight});
801  //  $("#classes-nav .jspContainer").css({height:classesHeight});
802}
803
804
805
806/* ######### END RESIZE THE SIDENAV HEIGHT ########## */
807
808
809
810
811
812/** Scroll the jScrollPane to make the currently selected item visible
813    This is called when the page finished loading. */
814function scrollIntoView(nav) {
815  var $nav = $("#"+nav);
816  var element = $nav.jScrollPane({/* ...settings... */});
817  var api = element.data('jsp');
818
819  if ($nav.is(':visible')) {
820    var $selected = $(".selected", $nav);
821    if ($selected.length == 0) return;
822
823    var selectedOffset = $selected.position().top;
824    if (selectedOffset + 90 > $nav.height()) {  // add 90 so that we scroll up even
825                                                // if the current item is close to the bottom
826      api.scrollTo(0, selectedOffset - ($nav.height() / 4), false); // scroll the item into view
827                                                              // to be 1/4 of the way from the top
828    }
829  }
830}
831
832
833
834
835
836
837/* Show popup dialogs */
838function showDialog(id) {
839  $dialog = $("#"+id);
840  $dialog.prepend('<div class="box-border"><div class="top"> <div class="left"></div> <div class="right"></div></div><div class="bottom"> <div class="left"></div> <div class="right"></div> </div> </div>');
841  $dialog.wrapInner('<div/>');
842  $dialog.removeClass("hide");
843}
844
845
846
847
848
849/* #########    COOKIES!     ########## */
850
851function readCookie(cookie) {
852  var myCookie = cookie_namespace+"_"+cookie+"=";
853  if (document.cookie) {
854    var index = document.cookie.indexOf(myCookie);
855    if (index != -1) {
856      var valStart = index + myCookie.length;
857      var valEnd = document.cookie.indexOf(";", valStart);
858      if (valEnd == -1) {
859        valEnd = document.cookie.length;
860      }
861      var val = document.cookie.substring(valStart, valEnd);
862      return val;
863    }
864  }
865  return 0;
866}
867
868function writeCookie(cookie, val, section, expiration) {
869  if (val==undefined) return;
870  section = section == null ? "_" : "_"+section+"_";
871  if (expiration == null) {
872    var date = new Date();
873    date.setTime(date.getTime()+(10*365*24*60*60*1000)); // default expiration is one week
874    expiration = date.toGMTString();
875  }
876  var cookieValue = cookie_namespace + section + cookie + "=" + val
877                    + "; expires=" + expiration+"; path=/";
878  document.cookie = cookieValue;
879}
880
881/* #########     END COOKIES!     ########## */
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907/*
908
909REMEMBER THE PREVIOUS PAGE FOR EACH TAB
910
911function loadLast(cookiePath) {
912  var location = window.location.href;
913  if (location.indexOf("/"+cookiePath+"/") != -1) {
914    return true;
915  }
916  var lastPage = readCookie(cookiePath + "_lastpage");
917  if (lastPage) {
918    window.location = lastPage;
919    return false;
920  }
921  return true;
922}
923
924
925
926$(window).unload(function(){
927  var path = getBaseUri(location.pathname);
928  if (path.indexOf("/reference/") != -1) {
929    writeCookie("lastpage", path, "reference", null);
930  } else if (path.indexOf("/guide/") != -1) {
931    writeCookie("lastpage", path, "guide", null);
932  } else if ((path.indexOf("/resources/") != -1) || (path.indexOf("/training/") != -1)) {
933    writeCookie("lastpage", path, "resources", null);
934  }
935});
936
937*/
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952function toggle(obj, slide) {
953  var ul = $("ul:first", obj);
954  var li = ul.parent();
955  if (li.hasClass("closed")) {
956    if (slide) {
957      ul.slideDown("fast");
958    } else {
959      ul.show();
960    }
961    li.removeClass("closed");
962    li.addClass("open");
963    $(".toggle-img", li).attr("title", "hide pages");
964  } else {
965    ul.slideUp("fast");
966    li.removeClass("open");
967    li.addClass("closed");
968    $(".toggle-img", li).attr("title", "show pages");
969  }
970}
971
972
973
974
975
976function buildToggleLists() {
977  $(".toggle-list").each(
978    function(i) {
979      $("div:first", this).append("<a class='toggle-img' href='#' title='show pages' onClick='toggle(this.parentNode.parentNode, true); return false;'></a>");
980      $(this).addClass("closed");
981    });
982}
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015/*      REFERENCE NAV SWAP     */
1016
1017
1018function getNavPref() {
1019  var v = readCookie('reference_nav');
1020  if (v != NAV_PREF_TREE) {
1021    v = NAV_PREF_PANELS;
1022  }
1023  return v;
1024}
1025
1026function chooseDefaultNav() {
1027  nav_pref = getNavPref();
1028  if (nav_pref == NAV_PREF_TREE) {
1029    $("#nav-panels").toggle();
1030    $("#panel-link").toggle();
1031    $("#nav-tree").toggle();
1032    $("#tree-link").toggle();
1033  }
1034}
1035
1036function swapNav() {
1037  if (nav_pref == NAV_PREF_TREE) {
1038    nav_pref = NAV_PREF_PANELS;
1039  } else {
1040    nav_pref = NAV_PREF_TREE;
1041    init_default_navtree(toRoot);
1042  }
1043  var date = new Date();
1044  date.setTime(date.getTime()+(10*365*24*60*60*1000)); // keep this for 10 years
1045  writeCookie("nav", nav_pref, "reference", date.toGMTString());
1046
1047  $("#nav-panels").toggle();
1048  $("#panel-link").toggle();
1049  $("#nav-tree").toggle();
1050  $("#tree-link").toggle();
1051
1052  resizeNav();
1053
1054  // Gross nasty hack to make tree view show up upon first swap by setting height manually
1055  $("#nav-tree .jspContainer:visible")
1056      .css({'height':$("#nav-tree .jspContainer .jspPane").height() +'px'});
1057  // Another nasty hack to make the scrollbar appear now that we have height
1058  resizeNav();
1059
1060  if ($("#nav-tree").is(':visible')) {
1061    scrollIntoView("nav-tree");
1062  } else {
1063    scrollIntoView("packages-nav");
1064    scrollIntoView("classes-nav");
1065  }
1066}
1067
1068
1069
1070/* ############################################ */
1071/* ##########     LOCALIZATION     ############ */
1072/* ############################################ */
1073
1074function getBaseUri(uri) {
1075  var intlUrl = (uri.substring(0,6) == "/intl/");
1076  if (intlUrl) {
1077    base = uri.substring(uri.indexOf('intl/')+5,uri.length);
1078    base = base.substring(base.indexOf('/')+1, base.length);
1079      //alert("intl, returning base url: /" + base);
1080    return ("/" + base);
1081  } else {
1082      //alert("not intl, returning uri as found.");
1083    return uri;
1084  }
1085}
1086
1087function requestAppendHL(uri) {
1088//append "?hl=<lang> to an outgoing request (such as to blog)
1089  var lang = getLangPref();
1090  if (lang) {
1091    var q = 'hl=' + lang;
1092    uri += '?' + q;
1093    window.location = uri;
1094    return false;
1095  } else {
1096    return true;
1097  }
1098}
1099
1100
1101function changeNavLang(lang) {
1102  var $links = $("#devdoc-nav,#header,#nav-x,.training-nav-top,.content-footer").find("a["+lang+"-lang]");
1103  $links.each(function(i){ // for each link with a translation
1104    var $link = $(this);
1105    if (lang != "en") { // No need to worry about English, because a language change invokes new request
1106      // put the desired language from the attribute as the text
1107      $link.text($link.attr(lang+"-lang"))
1108    }
1109  });
1110}
1111
1112function changeLangPref(lang, submit) {
1113  var date = new Date();
1114  expires = date.toGMTString(date.setTime(date.getTime()+(10*365*24*60*60*1000)));
1115  // keep this for 50 years
1116  //alert("expires: " + expires)
1117  writeCookie("pref_lang", lang, null, expires);
1118
1119  //  #######  TODO:  Remove this condition once we're stable on devsite #######
1120  //  This condition is only needed if we still need to support legacy GAE server
1121  if (devsite) {
1122    // Switch language when on Devsite server
1123    if (submit) {
1124      $("#setlang").submit();
1125    }
1126  } else {
1127    // Switch language when on legacy GAE server
1128    if (submit) {
1129      window.location = getBaseUri(location.pathname);
1130    }
1131  }
1132}
1133
1134function loadLangPref() {
1135  var lang = readCookie("pref_lang");
1136  if (lang != 0) {
1137    $("#language").find("option[value='"+lang+"']").attr("selected",true);
1138  }
1139}
1140
1141function getLangPref() {
1142  var lang = $("#language").find(":selected").attr("value");
1143  if (!lang) {
1144    lang = readCookie("pref_lang");
1145  }
1146  return (lang != 0) ? lang : 'en';
1147}
1148
1149/* ##########     END LOCALIZATION     ############ */
1150
1151
1152
1153
1154
1155
1156/* Used to hide and reveal supplemental content, such as long code samples.
1157   See the companion CSS in android-developer-docs.css */
1158function toggleContent(obj) {
1159  var div = $(obj.parentNode.parentNode);
1160  var toggleMe = $(".toggle-content-toggleme",div);
1161  if (div.hasClass("closed")) { // if it's closed, open it
1162    toggleMe.slideDown();
1163    $(".toggle-content-text", obj).toggle();
1164    div.removeClass("closed").addClass("open");
1165    $(".toggle-content-img", div).attr("title", "hide").attr("src", toRoot
1166                  + "assets/images/triangle-opened.png");
1167  } else { // if it's open, close it
1168    toggleMe.slideUp('fast', function() {  // Wait until the animation is done before closing arrow
1169      $(".toggle-content-text", obj).toggle();
1170      div.removeClass("open").addClass("closed");
1171      $(".toggle-content-img", div).attr("title", "show").attr("src", toRoot
1172                  + "assets/images/triangle-closed.png");
1173    });
1174  }
1175  return false;
1176}
1177
1178
1179/* New version of expandable content */
1180function toggleExpandable(link,id) {
1181  if($(id).is(':visible')) {
1182    $(id).slideUp();
1183    $(link).removeClass('expanded');
1184  } else {
1185    $(id).slideDown();
1186    $(link).addClass('expanded');
1187  }
1188}
1189
1190function hideExpandable(ids) {
1191  $(ids).slideUp();
1192  $(ids).prev('h4').find('a.expandable').removeClass('expanded');
1193}
1194
1195
1196
1197
1198
1199/*
1200 *  Slideshow 1.0
1201 *  Used on /index.html and /develop/index.html for carousel
1202 *
1203 *  Sample usage:
1204 *  HTML -
1205 *  <div class="slideshow-container">
1206 *   <a href="" class="slideshow-prev">Prev</a>
1207 *   <a href="" class="slideshow-next">Next</a>
1208 *   <ul>
1209 *       <li class="item"><img src="images/marquee1.jpg"></li>
1210 *       <li class="item"><img src="images/marquee2.jpg"></li>
1211 *       <li class="item"><img src="images/marquee3.jpg"></li>
1212 *       <li class="item"><img src="images/marquee4.jpg"></li>
1213 *   </ul>
1214 *  </div>
1215 *
1216 *   <script type="text/javascript">
1217 *   $('.slideshow-container').dacSlideshow({
1218 *       auto: true,
1219 *       btnPrev: '.slideshow-prev',
1220 *       btnNext: '.slideshow-next'
1221 *   });
1222 *   </script>
1223 *
1224 *  Options:
1225 *  btnPrev:    optional identifier for previous button
1226 *  btnNext:    optional identifier for next button
1227 *  btnPause:   optional identifier for pause button
1228 *  auto:       whether or not to auto-proceed
1229 *  speed:      animation speed
1230 *  autoTime:   time between auto-rotation
1231 *  easing:     easing function for transition
1232 *  start:      item to select by default
1233 *  scroll:     direction to scroll in
1234 *  pagination: whether or not to include dotted pagination
1235 *
1236 */
1237
1238 (function($) {
1239 $.fn.dacSlideshow = function(o) {
1240
1241     //Options - see above
1242     o = $.extend({
1243         btnPrev:   null,
1244         btnNext:   null,
1245         btnPause:  null,
1246         auto:      true,
1247         speed:     500,
1248         autoTime:  12000,
1249         easing:    null,
1250         start:     0,
1251         scroll:    1,
1252         pagination: true
1253
1254     }, o || {});
1255
1256     //Set up a carousel for each
1257     return this.each(function() {
1258
1259         var running = false;
1260         var animCss = o.vertical ? "top" : "left";
1261         var sizeCss = o.vertical ? "height" : "width";
1262         var div = $(this);
1263         var ul = $("ul", div);
1264         var tLi = $("li", ul);
1265         var tl = tLi.size();
1266         var timer = null;
1267
1268         var li = $("li", ul);
1269         var itemLength = li.size();
1270         var curr = o.start;
1271
1272         li.css({float: o.vertical ? "none" : "left"});
1273         ul.css({margin: "0", padding: "0", position: "relative", "list-style-type": "none", "z-index": "1"});
1274         div.css({position: "relative", "z-index": "2", left: "0px"});
1275
1276         var liSize = o.vertical ? height(li) : width(li);
1277         var ulSize = liSize * itemLength;
1278         var divSize = liSize;
1279
1280         li.css({width: li.width(), height: li.height()});
1281         ul.css(sizeCss, ulSize+"px").css(animCss, -(curr*liSize));
1282
1283         div.css(sizeCss, divSize+"px");
1284
1285         //Pagination
1286         if (o.pagination) {
1287             var pagination = $("<div class='pagination'></div>");
1288             var pag_ul = $("<ul></ul>");
1289             if (tl > 1) {
1290               for (var i=0;i<tl;i++) {
1291                    var li = $("<li>"+i+"</li>");
1292                    pag_ul.append(li);
1293                    if (i==o.start) li.addClass('active');
1294                        li.click(function() {
1295                        go(parseInt($(this).text()));
1296                    })
1297                }
1298                pagination.append(pag_ul);
1299                div.append(pagination);
1300             }
1301         }
1302
1303         //Previous button
1304         if(o.btnPrev)
1305             $(o.btnPrev).click(function(e) {
1306                 e.preventDefault();
1307                 return go(curr-o.scroll);
1308             });
1309
1310         //Next button
1311         if(o.btnNext)
1312             $(o.btnNext).click(function(e) {
1313                 e.preventDefault();
1314                 return go(curr+o.scroll);
1315             });
1316
1317         //Pause button
1318         if(o.btnPause)
1319             $(o.btnPause).click(function(e) {
1320                 e.preventDefault();
1321                 if ($(this).hasClass('paused')) {
1322                     startRotateTimer();
1323                 } else {
1324                     pauseRotateTimer();
1325                 }
1326             });
1327
1328         //Auto rotation
1329         if(o.auto) startRotateTimer();
1330
1331         function startRotateTimer() {
1332             clearInterval(timer);
1333             timer = setInterval(function() {
1334                  if (curr == tl-1) {
1335                    go(0);
1336                  } else {
1337                    go(curr+o.scroll);
1338                  }
1339              }, o.autoTime);
1340             $(o.btnPause).removeClass('paused');
1341         }
1342
1343         function pauseRotateTimer() {
1344             clearInterval(timer);
1345             $(o.btnPause).addClass('paused');
1346         }
1347
1348         //Go to an item
1349         function go(to) {
1350             if(!running) {
1351
1352                 if(to<0) {
1353                    to = itemLength-1;
1354                 } else if (to>itemLength-1) {
1355                    to = 0;
1356                 }
1357                 curr = to;
1358
1359                 running = true;
1360
1361                 ul.animate(
1362                     animCss == "left" ? { left: -(curr*liSize) } : { top: -(curr*liSize) } , o.speed, o.easing,
1363                     function() {
1364                         running = false;
1365                     }
1366                 );
1367
1368                 $(o.btnPrev + "," + o.btnNext).removeClass("disabled");
1369                 $( (curr-o.scroll<0 && o.btnPrev)
1370                     ||
1371                    (curr+o.scroll > itemLength && o.btnNext)
1372                     ||
1373                    []
1374                  ).addClass("disabled");
1375
1376
1377                 var nav_items = $('li', pagination);
1378                 nav_items.removeClass('active');
1379                 nav_items.eq(to).addClass('active');
1380
1381
1382             }
1383             if(o.auto) startRotateTimer();
1384             return false;
1385         };
1386     });
1387 };
1388
1389 function css(el, prop) {
1390     return parseInt($.css(el[0], prop)) || 0;
1391 };
1392 function width(el) {
1393     return  el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1394 };
1395 function height(el) {
1396     return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1397 };
1398
1399 })(jQuery);
1400
1401
1402/*
1403 *  dacSlideshow 1.0
1404 *  Used on develop/index.html for side-sliding tabs
1405 *
1406 *  Sample usage:
1407 *  HTML -
1408 *  <div class="slideshow-container">
1409 *   <a href="" class="slideshow-prev">Prev</a>
1410 *   <a href="" class="slideshow-next">Next</a>
1411 *   <ul>
1412 *       <li class="item"><img src="images/marquee1.jpg"></li>
1413 *       <li class="item"><img src="images/marquee2.jpg"></li>
1414 *       <li class="item"><img src="images/marquee3.jpg"></li>
1415 *       <li class="item"><img src="images/marquee4.jpg"></li>
1416 *   </ul>
1417 *  </div>
1418 *
1419 *   <script type="text/javascript">
1420 *   $('.slideshow-container').dacSlideshow({
1421 *       auto: true,
1422 *       btnPrev: '.slideshow-prev',
1423 *       btnNext: '.slideshow-next'
1424 *   });
1425 *   </script>
1426 *
1427 *  Options:
1428 *  btnPrev:    optional identifier for previous button
1429 *  btnNext:    optional identifier for next button
1430 *  auto:       whether or not to auto-proceed
1431 *  speed:      animation speed
1432 *  autoTime:   time between auto-rotation
1433 *  easing:     easing function for transition
1434 *  start:      item to select by default
1435 *  scroll:     direction to scroll in
1436 *  pagination: whether or not to include dotted pagination
1437 *
1438 */
1439 (function($) {
1440 $.fn.dacTabbedList = function(o) {
1441
1442     //Options - see above
1443     o = $.extend({
1444         speed : 250,
1445         easing: null,
1446         nav_id: null,
1447         frame_id: null
1448     }, o || {});
1449
1450     //Set up a carousel for each
1451     return this.each(function() {
1452
1453         var curr = 0;
1454         var running = false;
1455         var animCss = "margin-left";
1456         var sizeCss = "width";
1457         var div = $(this);
1458
1459         var nav = $(o.nav_id, div);
1460         var nav_li = $("li", nav);
1461         var nav_size = nav_li.size();
1462         var frame = div.find(o.frame_id);
1463         var content_width = $(frame).find('ul').width();
1464         //Buttons
1465         $(nav_li).click(function(e) {
1466           go($(nav_li).index($(this)));
1467         })
1468
1469         //Go to an item
1470         function go(to) {
1471             if(!running) {
1472                 curr = to;
1473                 running = true;
1474
1475                 frame.animate({ 'margin-left' : -(curr*content_width) }, o.speed, o.easing,
1476                     function() {
1477                         running = false;
1478                     }
1479                 );
1480
1481
1482                 nav_li.removeClass('active');
1483                 nav_li.eq(to).addClass('active');
1484
1485
1486             }
1487             return false;
1488         };
1489     });
1490 };
1491
1492 function css(el, prop) {
1493     return parseInt($.css(el[0], prop)) || 0;
1494 };
1495 function width(el) {
1496     return  el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1497 };
1498 function height(el) {
1499     return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1500 };
1501
1502 })(jQuery);
1503
1504
1505
1506
1507
1508/* ######################################################## */
1509/* ################  SEARCH SUGGESTIONS  ################## */
1510/* ######################################################## */
1511
1512
1513
1514var gSelectedIndex = -1;  // the index position of currently highlighted suggestion
1515var gSelectedColumn = -1;  // which column of suggestion lists is currently focused
1516
1517var gMatches = new Array();
1518var gLastText = "";
1519var gInitialized = false;
1520var ROW_COUNT_FRAMEWORK = 20;       // max number of results in list
1521var gListLength = 0;
1522
1523
1524var gGoogleMatches = new Array();
1525var ROW_COUNT_GOOGLE = 15;          // max number of results in list
1526var gGoogleListLength = 0;
1527
1528var gDocsMatches = new Array();
1529var ROW_COUNT_DOCS = 100;          // max number of results in list
1530var gDocsListLength = 0;
1531
1532function onSuggestionClick(link) {
1533  // When user clicks a suggested document, track it
1534  _gaq.push(['_trackEvent', 'Suggestion Click', 'clicked: ' + $(link).text(),
1535            'from: ' + $("#search_autocomplete").val()]);
1536}
1537
1538function set_item_selected($li, selected)
1539{
1540    if (selected) {
1541        $li.attr('class','jd-autocomplete jd-selected');
1542    } else {
1543        $li.attr('class','jd-autocomplete');
1544    }
1545}
1546
1547function set_item_values(toroot, $li, match)
1548{
1549    var $link = $('a',$li);
1550    $link.html(match.__hilabel || match.label);
1551    $link.attr('href',toroot + match.link);
1552}
1553
1554function new_suggestion($list) {
1555    var $li = $("<li class='jd-autocomplete'></li>");
1556    $list.append($li);
1557
1558    $li.mousedown(function() {
1559        window.location = this.firstChild.getAttribute("href");
1560    });
1561    $li.mouseover(function() {
1562        $('.search_filtered_wrapper li').removeClass('jd-selected');
1563        $(this).addClass('jd-selected');
1564        gSelectedColumn = $(".search_filtered:visible").index($(this).closest('.search_filtered'));
1565        gSelectedIndex = $("li", $(".search_filtered:visible")[gSelectedColumn]).index(this);
1566    });
1567    $li.append("<a onclick='onSuggestionClick(this)'></a>");
1568    $li.attr('class','show-item');
1569    return $li;
1570}
1571
1572function sync_selection_table(toroot)
1573{
1574    var $li; //list item jquery object
1575    var i; //list item iterator
1576
1577    // if there are NO results at all, hide all columns
1578    if (!(gMatches.length > 0) && !(gGoogleMatches.length > 0) && !(gDocsMatches.length > 0)) {
1579        $('.suggest-card').hide(300);
1580        return;
1581    }
1582
1583    // if there are api results
1584    if ((gMatches.length > 0) || (gGoogleMatches.length > 0)) {
1585      // reveal suggestion list
1586      $('.suggest-card.dummy').show();
1587      $('.suggest-card.reference').show();
1588      var listIndex = 0; // list index position
1589
1590      // reset the lists
1591      $(".search_filtered_wrapper.reference li").remove();
1592
1593      // ########### ANDROID RESULTS #############
1594      if (gMatches.length > 0) {
1595
1596          // determine android results to show
1597          gListLength = gMatches.length < ROW_COUNT_FRAMEWORK ?
1598                        gMatches.length : ROW_COUNT_FRAMEWORK;
1599          for (i=0; i<gListLength; i++) {
1600              var $li = new_suggestion($(".suggest-card.reference ul"));
1601              set_item_values(toroot, $li, gMatches[i]);
1602              set_item_selected($li, i == gSelectedIndex);
1603          }
1604      }
1605
1606      // ########### GOOGLE RESULTS #############
1607      if (gGoogleMatches.length > 0) {
1608          // show header for list
1609          $(".suggest-card.reference ul").append("<li class='header'>in Google Services:</li>");
1610
1611          // determine google results to show
1612          gGoogleListLength = gGoogleMatches.length < ROW_COUNT_GOOGLE ? gGoogleMatches.length : ROW_COUNT_GOOGLE;
1613          for (i=0; i<gGoogleListLength; i++) {
1614              var $li = new_suggestion($(".suggest-card.reference ul"));
1615              set_item_values(toroot, $li, gGoogleMatches[i]);
1616              set_item_selected($li, i == gSelectedIndex);
1617          }
1618      }
1619    } else {
1620      $('.suggest-card.reference').hide();
1621      $('.suggest-card.dummy').hide();
1622    }
1623
1624    // ########### JD DOC RESULTS #############
1625    if (gDocsMatches.length > 0) {
1626        // reset the lists
1627        $(".search_filtered_wrapper.docs li").remove();
1628
1629        // determine google results to show
1630        gDocsListLength = gDocsMatches.length < ROW_COUNT_DOCS ? gDocsMatches.length : ROW_COUNT_DOCS;
1631        for (i=0; i<gDocsListLength; i++) {
1632            var sugg = gDocsMatches[i];
1633            var $li;
1634            if (sugg.type == "design") {
1635                $li = new_suggestion($(".suggest-card.design ul"));
1636            } else
1637            if (sugg.type == "distribute") {
1638                $li = new_suggestion($(".suggest-card.distribute ul"));
1639            } else
1640            if (sugg.type == "training") {
1641                $li = new_suggestion($(".suggest-card.develop .child-card.training"));
1642            } else
1643            if (sugg.type == "guide"||"google") {
1644                $li = new_suggestion($(".suggest-card.develop .child-card.guides"));
1645            } else {
1646              continue;
1647            }
1648
1649            set_item_values(toroot, $li, sugg);
1650            set_item_selected($li, i == gSelectedIndex);
1651        }
1652
1653        // add heading and show or hide card
1654        if ($(".suggest-card.design li").length > 0) {
1655          $(".suggest-card.design ul").prepend("<li class='header'>Design:</li>");
1656          $(".suggest-card.design").show(300);
1657        } else {
1658          $('.suggest-card.design').hide(300);
1659        }
1660        if ($(".suggest-card.distribute li").length > 0) {
1661          $(".suggest-card.distribute ul").prepend("<li class='header'>Distribute:</li>");
1662          $(".suggest-card.distribute").show(300);
1663        } else {
1664          $('.suggest-card.distribute').hide(300);
1665        }
1666        if ($(".child-card.guides li").length > 0) {
1667          $(".child-card.guides").prepend("<li class='header'>Guides:</li>");
1668          $(".child-card.guides li").appendTo(".suggest-card.develop ul");
1669        }
1670        if ($(".child-card.training li").length > 0) {
1671          $(".child-card.training").prepend("<li class='header'>Training:</li>");
1672          $(".child-card.training li").appendTo(".suggest-card.develop ul");
1673        }
1674
1675        if ($(".suggest-card.develop li").length > 0) {
1676          $(".suggest-card.develop").show(300);
1677        } else {
1678          $('.suggest-card.develop').hide(300);
1679        }
1680
1681    } else {
1682      $('.search_filtered_wrapper.docs .suggest-card:not(.dummy)').hide(300);
1683    }
1684}
1685
1686/** Called by the search input's onkeydown and onkeyup events.
1687  * Handles navigation with keyboard arrows, Enter key to invoke search,
1688  * otherwise invokes search suggestions on key-up event.
1689  * @param e       The JS event
1690  * @param kd      True if the event is key-down
1691  * @param toroot  A string for the site's root path
1692  * @returns       True if the event should bubble up
1693  */
1694function search_changed(e, kd, toroot)
1695{
1696    var search = document.getElementById("search_autocomplete");
1697    var text = search.value.replace(/(^ +)|( +$)/g, '');
1698    // get the ul hosting the currently selected item
1699    gSelectedColumn = gSelectedColumn >= 0 ? gSelectedColumn :  0;
1700    var $columns = $(".search_filtered_wrapper").find(".search_filtered:visible");
1701    var $selectedUl = $columns[gSelectedColumn];
1702
1703    // show/hide the close button
1704    if (text != '') {
1705        $(".search .close").removeClass("hide");
1706    } else {
1707        $(".search .close").addClass("hide");
1708    }
1709    // 27 = esc
1710    if (e.keyCode == 27) {
1711        // close all search results
1712        if (kd) $('.search .close').trigger('click');
1713        return true;
1714    }
1715    // 13 = enter
1716    else if (e.keyCode == 13) {
1717        if (gSelectedIndex < 0) {
1718            $('.suggest-card').hide();
1719            if ($("#searchResults").is(":hidden") && (search.value != "")) {
1720              // if results aren't showing (and text not empty), return true to allow search to execute
1721              return true;
1722            } else {
1723              // otherwise, results are already showing, so allow ajax to auto refresh the results
1724              // and ignore this Enter press to avoid the reload.
1725              return false;
1726            }
1727        } else if (kd && gSelectedIndex >= 0) {
1728            // click the link corresponding to selected item
1729            $("a",$("li",$selectedUl)[gSelectedIndex]).get()[0].click();
1730            return false;
1731        }
1732    }
1733    // Stop here if Google results are showing
1734    else if ($("#searchResults").is(":visible")) {
1735        return true;
1736    }
1737    // 38 UP ARROW
1738    else if (kd && (e.keyCode == 38)) {
1739        // if the next item is a header, skip it
1740        if ($($("li", $selectedUl)[gSelectedIndex-1]).hasClass("header")) {
1741            gSelectedIndex--;
1742        }
1743        if (gSelectedIndex >= 0) {
1744            $('li', $selectedUl).removeClass('jd-selected');
1745            gSelectedIndex--;
1746            $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
1747            // If user reaches top, reset selected column
1748            if (gSelectedIndex < 0) {
1749              gSelectedColumn = -1;
1750            }
1751        }
1752        return false;
1753    }
1754    // 40 DOWN ARROW
1755    else if (kd && (e.keyCode == 40)) {
1756        // if the next item is a header, skip it
1757        if ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header")) {
1758            gSelectedIndex++;
1759        }
1760        if ((gSelectedIndex < $("li", $selectedUl).length-1) ||
1761                        ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header"))) {
1762            $('li', $selectedUl).removeClass('jd-selected');
1763            gSelectedIndex++;
1764            $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
1765        }
1766        return false;
1767    }
1768    // Consider left/right arrow navigation
1769    // NOTE: Order of suggest columns are reverse order (index position 0 is on right)
1770    else if (kd && $columns.length > 1 && gSelectedColumn >= 0) {
1771      // 37 LEFT ARROW
1772      // go left only if current column is not left-most column (last column)
1773      if (e.keyCode == 37 && gSelectedColumn < $columns.length - 1) {
1774        $('li', $selectedUl).removeClass('jd-selected');
1775        gSelectedColumn++;
1776        $selectedUl = $columns[gSelectedColumn];
1777        // keep or reset the selected item to last item as appropriate
1778        gSelectedIndex = gSelectedIndex >
1779                $("li", $selectedUl).length-1 ?
1780                $("li", $selectedUl).length-1 : gSelectedIndex;
1781        // if the corresponding item is a header, move down
1782        if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) {
1783          gSelectedIndex++;
1784        }
1785        // set item selected
1786        $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
1787        return false;
1788      }
1789      // 39 RIGHT ARROW
1790      // go right only if current column is not the right-most column (first column)
1791      else if (e.keyCode == 39 && gSelectedColumn > 0) {
1792        $('li', $selectedUl).removeClass('jd-selected');
1793        gSelectedColumn--;
1794        $selectedUl = $columns[gSelectedColumn];
1795        // keep or reset the selected item to last item as appropriate
1796        gSelectedIndex = gSelectedIndex >
1797                $("li", $selectedUl).length-1 ?
1798                $("li", $selectedUl).length-1 : gSelectedIndex;
1799        // if the corresponding item is a header, move down
1800        if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) {
1801          gSelectedIndex++;
1802        }
1803        // set item selected
1804        $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
1805        return false;
1806      }
1807    }
1808
1809    // if key-up event and not arrow down/up,
1810    // read the search query and add suggestsions to gMatches
1811    else if (!kd && (e.keyCode != 40)
1812                 && (e.keyCode != 38)
1813                 && (e.keyCode != 37)
1814                 && (e.keyCode != 39)) {
1815        gSelectedIndex = -1;
1816        gMatches = new Array();
1817        matchedCount = 0;
1818        gGoogleMatches = new Array();
1819        matchedCountGoogle = 0;
1820        gDocsMatches = new Array();
1821        matchedCountDocs = 0;
1822
1823        // Search for Android matches
1824        for (var i=0; i<DATA.length; i++) {
1825            var s = DATA[i];
1826            if (text.length != 0 &&
1827                  s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
1828                gMatches[matchedCount] = s;
1829                matchedCount++;
1830            }
1831        }
1832        rank_autocomplete_api_results(text, gMatches);
1833        for (var i=0; i<gMatches.length; i++) {
1834            var s = gMatches[i];
1835        }
1836
1837
1838        // Search for Google matches
1839        for (var i=0; i<GOOGLE_DATA.length; i++) {
1840            var s = GOOGLE_DATA[i];
1841            if (text.length != 0 &&
1842                  s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
1843                gGoogleMatches[matchedCountGoogle] = s;
1844                matchedCountGoogle++;
1845            }
1846        }
1847        rank_autocomplete_api_results(text, gGoogleMatches);
1848        for (var i=0; i<gGoogleMatches.length; i++) {
1849            var s = gGoogleMatches[i];
1850        }
1851
1852        highlight_autocomplete_result_labels(text);
1853
1854
1855
1856        // Search for JD docs
1857        if (text.length >= 3) {
1858          for (var i=0; i<JD_DATA.length; i++) {
1859            // Regex to match only the beginning of a word
1860            var textRegex = new RegExp("\\b" + text.toLowerCase(), "g");
1861            // current search comparison, with counters for tag and title,
1862            // used later to improve ranking
1863            var s = JD_DATA[i];
1864            s.matched_tag = 0;
1865            s.matched_title = 0;
1866            var matched = false;
1867
1868            // Check if query matches any tags; work backwards toward 1 to assist ranking
1869            for (var j = s.tags.length - 1; j >= 0; j--) {
1870              // it matches a tag
1871              if (s.tags[j].toLowerCase().match(textRegex)) {
1872                matched = true;
1873                s.matched_tag = j + 1; // add 1 to index position
1874              }
1875            }
1876            // Don't consider doc title for lessons (only for class landing pages)
1877            // ...it is not a training lesson (or is but has matched a tag)
1878            if (!(s.type == "training" && s.link.indexOf("index.html") == -1) || matched) {
1879              // it matches the doc title
1880              if (s.label.toLowerCase().match(textRegex)) {
1881                matched = true;
1882                s.matched_title = 1;
1883              }
1884            }
1885            if (matched) {
1886              gDocsMatches[matchedCountDocs] = s;
1887              matchedCountDocs++;
1888            }
1889          }
1890          rank_autocomplete_doc_results(text, gDocsMatches);
1891        }
1892
1893        // draw the suggestions
1894        sync_selection_table(toroot);
1895        return true; // allow the event to bubble up to the search api
1896    }
1897}
1898
1899/* Order the jd doc result list based on match quality */
1900function rank_autocomplete_doc_results(query, matches) {
1901    query = query || '';
1902    if (!matches || !matches.length)
1903      return;
1904
1905    var _resultScoreFn = function(match) {
1906        var score = 1.0;
1907
1908        // if the query matched a tag
1909        if (match.matched_tag > 0) {
1910          // multiply score by factor relative to position in tags list (max of 3)
1911          score *= 3 / match.matched_tag;
1912
1913          // if it also matched the title
1914          if (match.matched_title > 0) {
1915            score *= 2;
1916          }
1917        } else if (match.matched_title > 0) {
1918          score *= 3;
1919        }
1920
1921        return score;
1922    };
1923
1924    for (var i=0; i<matches.length; i++) {
1925        matches[i].__resultScore = _resultScoreFn(matches[i]);
1926    }
1927
1928    matches.sort(function(a,b){
1929        var n = b.__resultScore - a.__resultScore;
1930        if (n == 0) // lexicographical sort if scores are the same
1931            n = (a.label < b.label) ? -1 : 1;
1932        return n;
1933    });
1934}
1935
1936/* Order the result list based on match quality */
1937function rank_autocomplete_api_results(query, matches) {
1938    query = query || '';
1939    if (!matches || !matches.length)
1940      return;
1941
1942    // helper function that gets the last occurence index of the given regex
1943    // in the given string, or -1 if not found
1944    var _lastSearch = function(s, re) {
1945      if (s == '')
1946        return -1;
1947      var l = -1;
1948      var tmp;
1949      while ((tmp = s.search(re)) >= 0) {
1950        if (l < 0) l = 0;
1951        l += tmp;
1952        s = s.substr(tmp + 1);
1953      }
1954      return l;
1955    };
1956
1957    // helper function that counts the occurrences of a given character in
1958    // a given string
1959    var _countChar = function(s, c) {
1960      var n = 0;
1961      for (var i=0; i<s.length; i++)
1962        if (s.charAt(i) == c) ++n;
1963      return n;
1964    };
1965
1966    var queryLower = query.toLowerCase();
1967    var queryAlnum = (queryLower.match(/\w+/) || [''])[0];
1968    var partPrefixAlnumRE = new RegExp('\\b' + queryAlnum);
1969    var partExactAlnumRE = new RegExp('\\b' + queryAlnum + '\\b');
1970
1971    var _resultScoreFn = function(result) {
1972        // scores are calculated based on exact and prefix matches,
1973        // and then number of path separators (dots) from the last
1974        // match (i.e. favoring classes and deep package names)
1975        var score = 1.0;
1976        var labelLower = result.label.toLowerCase();
1977        var t;
1978        t = _lastSearch(labelLower, partExactAlnumRE);
1979        if (t >= 0) {
1980            // exact part match
1981            var partsAfter = _countChar(labelLower.substr(t + 1), '.');
1982            score *= 200 / (partsAfter + 1);
1983        } else {
1984            t = _lastSearch(labelLower, partPrefixAlnumRE);
1985            if (t >= 0) {
1986                // part prefix match
1987                var partsAfter = _countChar(labelLower.substr(t + 1), '.');
1988                score *= 20 / (partsAfter + 1);
1989            }
1990        }
1991
1992        return score;
1993    };
1994
1995    for (var i=0; i<matches.length; i++) {
1996        // if the API is deprecated, default score is 0; otherwise, perform scoring
1997        if (matches[i].deprecated == "true") {
1998          matches[i].__resultScore = 0;
1999        } else {
2000          matches[i].__resultScore = _resultScoreFn(matches[i]);
2001        }
2002    }
2003
2004    matches.sort(function(a,b){
2005        var n = b.__resultScore - a.__resultScore;
2006        if (n == 0) // lexicographical sort if scores are the same
2007            n = (a.label < b.label) ? -1 : 1;
2008        return n;
2009    });
2010}
2011
2012/* Add emphasis to part of string that matches query */
2013function highlight_autocomplete_result_labels(query) {
2014    query = query || '';
2015    if ((!gMatches || !gMatches.length) && (!gGoogleMatches || !gGoogleMatches.length))
2016      return;
2017
2018    var queryLower = query.toLowerCase();
2019    var queryAlnumDot = (queryLower.match(/[\w\.]+/) || [''])[0];
2020    var queryRE = new RegExp(
2021        '(' + queryAlnumDot.replace(/\./g, '\\.') + ')', 'ig');
2022    for (var i=0; i<gMatches.length; i++) {
2023        gMatches[i].__hilabel = gMatches[i].label.replace(
2024            queryRE, '<b>$1</b>');
2025    }
2026    for (var i=0; i<gGoogleMatches.length; i++) {
2027        gGoogleMatches[i].__hilabel = gGoogleMatches[i].label.replace(
2028            queryRE, '<b>$1</b>');
2029    }
2030}
2031
2032function search_focus_changed(obj, focused)
2033{
2034    if (!focused) {
2035        if(obj.value == ""){
2036          $(".search .close").addClass("hide");
2037        }
2038        $(".suggest-card").hide();
2039    }
2040}
2041
2042function submit_search() {
2043  var query = document.getElementById('search_autocomplete').value;
2044  location.hash = 'q=' + query;
2045  loadSearchResults();
2046  $("#searchResults").slideDown('slow');
2047  return false;
2048}
2049
2050
2051function hideResults() {
2052  $("#searchResults").slideUp();
2053  $(".search .close").addClass("hide");
2054  location.hash = '';
2055
2056  $("#search_autocomplete").val("").blur();
2057
2058  // reset the ajax search callback to nothing, so results don't appear unless ENTER
2059  searchControl.setSearchStartingCallback(this, function(control, searcher, query) {});
2060
2061  // forcefully regain key-up event control (previously jacked by search api)
2062  $("#search_autocomplete").keyup(function(event) {
2063    return search_changed(event, false, toRoot);
2064  });
2065
2066  return false;
2067}
2068
2069
2070
2071/* ########################################################## */
2072/* ################  CUSTOM SEARCH ENGINE  ################## */
2073/* ########################################################## */
2074
2075var searchControl;
2076google.load('search', '1', {"callback" : function() {
2077            searchControl = new google.search.SearchControl();
2078          } });
2079
2080function loadSearchResults() {
2081  document.getElementById("search_autocomplete").style.color = "#000";
2082
2083  searchControl = new google.search.SearchControl();
2084
2085  // use our existing search form and use tabs when multiple searchers are used
2086  drawOptions = new google.search.DrawOptions();
2087  drawOptions.setDrawMode(google.search.SearchControl.DRAW_MODE_TABBED);
2088  drawOptions.setInput(document.getElementById("search_autocomplete"));
2089
2090  // configure search result options
2091  searchOptions = new google.search.SearcherOptions();
2092  searchOptions.setExpandMode(GSearchControl.EXPAND_MODE_OPEN);
2093
2094  // configure each of the searchers, for each tab
2095  devSiteSearcher = new google.search.WebSearch();
2096  devSiteSearcher.setUserDefinedLabel("All");
2097  devSiteSearcher.setSiteRestriction("001482626316274216503:zu90b7s047u");
2098
2099  designSearcher = new google.search.WebSearch();
2100  designSearcher.setUserDefinedLabel("Design");
2101  designSearcher.setSiteRestriction("http://developer.android.com/design/");
2102
2103  trainingSearcher = new google.search.WebSearch();
2104  trainingSearcher.setUserDefinedLabel("Training");
2105  trainingSearcher.setSiteRestriction("http://developer.android.com/training/");
2106
2107  guidesSearcher = new google.search.WebSearch();
2108  guidesSearcher.setUserDefinedLabel("Guides");
2109  guidesSearcher.setSiteRestriction("http://developer.android.com/guide/");
2110
2111  referenceSearcher = new google.search.WebSearch();
2112  referenceSearcher.setUserDefinedLabel("Reference");
2113  referenceSearcher.setSiteRestriction("http://developer.android.com/reference/");
2114
2115  googleSearcher = new google.search.WebSearch();
2116  googleSearcher.setUserDefinedLabel("Google Services");
2117  googleSearcher.setSiteRestriction("http://developer.android.com/google/");
2118
2119  blogSearcher = new google.search.WebSearch();
2120  blogSearcher.setUserDefinedLabel("Blog");
2121  blogSearcher.setSiteRestriction("http://android-developers.blogspot.com");
2122
2123  // add each searcher to the search control
2124  searchControl.addSearcher(devSiteSearcher, searchOptions);
2125  searchControl.addSearcher(designSearcher, searchOptions);
2126  searchControl.addSearcher(trainingSearcher, searchOptions);
2127  searchControl.addSearcher(guidesSearcher, searchOptions);
2128  searchControl.addSearcher(referenceSearcher, searchOptions);
2129  searchControl.addSearcher(googleSearcher, searchOptions);
2130  searchControl.addSearcher(blogSearcher, searchOptions);
2131
2132  // configure result options
2133  searchControl.setResultSetSize(google.search.Search.LARGE_RESULTSET);
2134  searchControl.setLinkTarget(google.search.Search.LINK_TARGET_SELF);
2135  searchControl.setTimeoutInterval(google.search.SearchControl.TIMEOUT_SHORT);
2136  searchControl.setNoResultsString(google.search.SearchControl.NO_RESULTS_DEFAULT_STRING);
2137
2138  // upon ajax search, refresh the url and search title
2139  searchControl.setSearchStartingCallback(this, function(control, searcher, query) {
2140    updateResultTitle(query);
2141    var query = document.getElementById('search_autocomplete').value;
2142    location.hash = 'q=' + query;
2143  });
2144
2145  // once search results load, set up click listeners
2146  searchControl.setSearchCompleteCallback(this, function(control, searcher, query) {
2147    addResultClickListeners();
2148  });
2149
2150  // draw the search results box
2151  searchControl.draw(document.getElementById("leftSearchControl"), drawOptions);
2152
2153  // get query and execute the search
2154  searchControl.execute(decodeURI(getQuery(location.hash)));
2155
2156  document.getElementById("search_autocomplete").focus();
2157  addTabListeners();
2158}
2159// End of loadSearchResults
2160
2161
2162google.setOnLoadCallback(function(){
2163  if (location.hash.indexOf("q=") == -1) {
2164    // if there's no query in the url, don't search and make sure results are hidden
2165    $('#searchResults').hide();
2166    return;
2167  } else {
2168    // first time loading search results for this page
2169    $('#searchResults').slideDown('slow');
2170    $(".search .close").removeClass("hide");
2171    loadSearchResults();
2172  }
2173}, true);
2174
2175// when an event on the browser history occurs (back, forward, load) requery hash and do search
2176$(window).hashchange( function(){
2177  // Exit if the hash isn't a search query or there's an error in the query
2178  if ((location.hash.indexOf("q=") == -1) || (query == "undefined")) {
2179    // If the results pane is open, close it.
2180    if (!$("#searchResults").is(":hidden")) {
2181      hideResults();
2182    }
2183    return;
2184  }
2185
2186  // Otherwise, we have a search to do
2187  var query = decodeURI(getQuery(location.hash));
2188  searchControl.execute(query);
2189  $('#searchResults').slideDown('slow');
2190  $("#search_autocomplete").focus();
2191  $(".search .close").removeClass("hide");
2192
2193  updateResultTitle(query);
2194});
2195
2196function updateResultTitle(query) {
2197  $("#searchTitle").html("Results for <em>" + escapeHTML(query) + "</em>");
2198}
2199
2200// forcefully regain key-up event control (previously jacked by search api)
2201$("#search_autocomplete").keyup(function(event) {
2202  return search_changed(event, false, toRoot);
2203});
2204
2205// add event listeners to each tab so we can track the browser history
2206function addTabListeners() {
2207  var tabHeaders = $(".gsc-tabHeader");
2208  for (var i = 0; i < tabHeaders.length; i++) {
2209    $(tabHeaders[i]).attr("id",i).click(function() {
2210    /*
2211      // make a copy of the page numbers for the search left pane
2212      setTimeout(function() {
2213        // remove any residual page numbers
2214        $('#searchResults .gsc-tabsArea .gsc-cursor-box.gs-bidi-start-align').remove();
2215        // move the page numbers to the left position; make a clone,
2216        // because the element is drawn to the DOM only once
2217        // and because we're going to remove it (previous line),
2218        // we need it to be available to move again as the user navigates
2219        $('#searchResults .gsc-webResult .gsc-cursor-box.gs-bidi-start-align:visible')
2220                        .clone().appendTo('#searchResults .gsc-tabsArea');
2221        }, 200);
2222      */
2223    });
2224  }
2225  setTimeout(function(){$(tabHeaders[0]).click()},200);
2226}
2227
2228// add analytics tracking events to each result link
2229function addResultClickListeners() {
2230  $("#searchResults a.gs-title").each(function(index, link) {
2231    // When user clicks enter for Google search results, track it
2232    $(link).click(function() {
2233      _gaq.push(['_trackEvent', 'Google Click', 'clicked: ' + $(this).text(),
2234                'from: ' + $("#search_autocomplete").val()]);
2235    });
2236  });
2237}
2238
2239
2240function getQuery(hash) {
2241  var queryParts = hash.split('=');
2242  return queryParts[1];
2243}
2244
2245/* returns the given string with all HTML brackets converted to entities
2246    TODO: move this to the site's JS library */
2247function escapeHTML(string) {
2248  return string.replace(/</g,"&lt;")
2249                .replace(/>/g,"&gt;");
2250}
2251
2252
2253
2254
2255
2256
2257
2258/* ######################################################## */
2259/* #################  JAVADOC REFERENCE ################### */
2260/* ######################################################## */
2261
2262/* Initialize some droiddoc stuff, but only if we're in the reference */
2263if (location.pathname.indexOf("/reference")) {
2264  if(!location.pathname.indexOf("/reference-gms/packages.html")
2265    && !location.pathname.indexOf("/reference-gcm/packages.html")
2266    && !location.pathname.indexOf("/reference/com/google") == 0) {
2267    $(document).ready(function() {
2268      // init available apis based on user pref
2269      changeApiLevel();
2270      initSidenavHeightResize()
2271      });
2272  }
2273}
2274
2275var API_LEVEL_COOKIE = "api_level";
2276var minLevel = 1;
2277var maxLevel = 1;
2278
2279/******* SIDENAV DIMENSIONS ************/
2280
2281  function initSidenavHeightResize() {
2282    // Change the drag bar size to nicely fit the scrollbar positions
2283    var $dragBar = $(".ui-resizable-s");
2284    $dragBar.css({'width': $dragBar.parent().width() - 5 + "px"});
2285
2286    $( "#resize-packages-nav" ).resizable({
2287      containment: "#nav-panels",
2288      handles: "s",
2289      alsoResize: "#packages-nav",
2290      resize: function(event, ui) { resizeNav(); }, /* resize the nav while dragging */
2291      stop: function(event, ui) { saveNavPanels(); } /* once stopped, save the sizes to cookie  */
2292      });
2293
2294  }
2295
2296function updateSidenavFixedWidth() {
2297  if (!navBarIsFixed) return;
2298  $('#devdoc-nav').css({
2299    'width' : $('#side-nav').css('width'),
2300    'margin' : $('#side-nav').css('margin')
2301  });
2302  $('#devdoc-nav a.totop').css({'display':'block','width':$("#nav").innerWidth()+'px'});
2303
2304  initSidenavHeightResize();
2305}
2306
2307function updateSidenavFullscreenWidth() {
2308  if (!navBarIsFixed) return;
2309  $('#devdoc-nav').css({
2310    'width' : $('#side-nav').css('width'),
2311    'margin' : $('#side-nav').css('margin')
2312  });
2313  $('#devdoc-nav .totop').css({'left': 'inherit'});
2314
2315  initSidenavHeightResize();
2316}
2317
2318function buildApiLevelSelector() {
2319  maxLevel = SINCE_DATA.length;
2320  var userApiLevel = parseInt(readCookie(API_LEVEL_COOKIE));
2321  userApiLevel = userApiLevel == 0 ? maxLevel : userApiLevel; // If there's no cookie (zero), use the max by default
2322
2323  minLevel = parseInt($("#doc-api-level").attr("class"));
2324  // Handle provisional api levels; the provisional level will always be the highest possible level
2325  // Provisional api levels will also have a length; other stuff that's just missing a level won't,
2326  // so leave those kinds of entities at the default level of 1 (for example, the R.styleable class)
2327  if (isNaN(minLevel) && minLevel.length) {
2328    minLevel = maxLevel;
2329  }
2330  var select = $("#apiLevelSelector").html("").change(changeApiLevel);
2331  for (var i = maxLevel-1; i >= 0; i--) {
2332    var option = $("<option />").attr("value",""+SINCE_DATA[i]).append(""+SINCE_DATA[i]);
2333  //  if (SINCE_DATA[i] < minLevel) option.addClass("absent"); // always false for strings (codenames)
2334    select.append(option);
2335  }
2336
2337  // get the DOM element and use setAttribute cuz IE6 fails when using jquery .attr('selected',true)
2338  var selectedLevelItem = $("#apiLevelSelector option[value='"+userApiLevel+"']").get(0);
2339  selectedLevelItem.setAttribute('selected',true);
2340}
2341
2342function changeApiLevel() {
2343  maxLevel = SINCE_DATA.length;
2344  var selectedLevel = maxLevel;
2345
2346  selectedLevel = parseInt($("#apiLevelSelector option:selected").val());
2347  toggleVisisbleApis(selectedLevel, "body");
2348
2349  var date = new Date();
2350  date.setTime(date.getTime()+(10*365*24*60*60*1000)); // keep this for 10 years
2351  var expiration = date.toGMTString();
2352  writeCookie(API_LEVEL_COOKIE, selectedLevel, null, expiration);
2353
2354  if (selectedLevel < minLevel) {
2355    var thing = ($("#jd-header").html().indexOf("package") != -1) ? "package" : "class";
2356    $("#naMessage").show().html("<div><p><strong>This " + thing
2357              + " requires API level " + minLevel + " or higher.</strong></p>"
2358              + "<p>This document is hidden because your selected API level for the documentation is "
2359              + selectedLevel + ". You can change the documentation API level with the selector "
2360              + "above the left navigation.</p>"
2361              + "<p>For more information about specifying the API level your app requires, "
2362              + "read <a href='" + toRoot + "training/basics/supporting-devices/platforms.html'"
2363              + ">Supporting Different Platform Versions</a>.</p>"
2364              + "<input type='button' value='OK, make this page visible' "
2365              + "title='Change the API level to " + minLevel + "' "
2366              + "onclick='$(\"#apiLevelSelector\").val(\"" + minLevel + "\");changeApiLevel();' />"
2367              + "</div>");
2368  } else {
2369    $("#naMessage").hide();
2370  }
2371}
2372
2373function toggleVisisbleApis(selectedLevel, context) {
2374  var apis = $(".api",context);
2375  apis.each(function(i) {
2376    var obj = $(this);
2377    var className = obj.attr("class");
2378    var apiLevelIndex = className.lastIndexOf("-")+1;
2379    var apiLevelEndIndex = className.indexOf(" ", apiLevelIndex);
2380    apiLevelEndIndex = apiLevelEndIndex != -1 ? apiLevelEndIndex : className.length;
2381    var apiLevel = className.substring(apiLevelIndex, apiLevelEndIndex);
2382    if (apiLevel.length == 0) { // for odd cases when the since data is actually missing, just bail
2383      return;
2384    }
2385    apiLevel = parseInt(apiLevel);
2386
2387    // Handle provisional api levels; if this item's level is the provisional one, set it to the max
2388    var selectedLevelNum = parseInt(selectedLevel)
2389    var apiLevelNum = parseInt(apiLevel);
2390    if (isNaN(apiLevelNum)) {
2391        apiLevelNum = maxLevel;
2392    }
2393
2394    // Grey things out that aren't available and give a tooltip title
2395    if (apiLevelNum > selectedLevelNum) {
2396      obj.addClass("absent").attr("title","Requires API Level \""
2397            + apiLevel + "\" or higher");
2398    }
2399    else obj.removeClass("absent").removeAttr("title");
2400  });
2401}
2402
2403
2404
2405
2406/* #################  SIDENAV TREE VIEW ################### */
2407
2408function new_node(me, mom, text, link, children_data, api_level)
2409{
2410  var node = new Object();
2411  node.children = Array();
2412  node.children_data = children_data;
2413  node.depth = mom.depth + 1;
2414
2415  node.li = document.createElement("li");
2416  mom.get_children_ul().appendChild(node.li);
2417
2418  node.label_div = document.createElement("div");
2419  node.label_div.className = "label";
2420  if (api_level != null) {
2421    $(node.label_div).addClass("api");
2422    $(node.label_div).addClass("api-level-"+api_level);
2423  }
2424  node.li.appendChild(node.label_div);
2425
2426  if (children_data != null) {
2427    node.expand_toggle = document.createElement("a");
2428    node.expand_toggle.href = "javascript:void(0)";
2429    node.expand_toggle.onclick = function() {
2430          if (node.expanded) {
2431            $(node.get_children_ul()).slideUp("fast");
2432            node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
2433            node.expanded = false;
2434          } else {
2435            expand_node(me, node);
2436          }
2437       };
2438    node.label_div.appendChild(node.expand_toggle);
2439
2440    node.plus_img = document.createElement("img");
2441    node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
2442    node.plus_img.className = "plus";
2443    node.plus_img.width = "8";
2444    node.plus_img.border = "0";
2445    node.expand_toggle.appendChild(node.plus_img);
2446
2447    node.expanded = false;
2448  }
2449
2450  var a = document.createElement("a");
2451  node.label_div.appendChild(a);
2452  node.label = document.createTextNode(text);
2453  a.appendChild(node.label);
2454  if (link) {
2455    a.href = me.toroot + link;
2456  } else {
2457    if (children_data != null) {
2458      a.className = "nolink";
2459      a.href = "javascript:void(0)";
2460      a.onclick = node.expand_toggle.onclick;
2461      // This next line shouldn't be necessary.  I'll buy a beer for the first
2462      // person who figures out how to remove this line and have the link
2463      // toggle shut on the first try. --joeo@android.com
2464      node.expanded = false;
2465    }
2466  }
2467
2468
2469  node.children_ul = null;
2470  node.get_children_ul = function() {
2471      if (!node.children_ul) {
2472        node.children_ul = document.createElement("ul");
2473        node.children_ul.className = "children_ul";
2474        node.children_ul.style.display = "none";
2475        node.li.appendChild(node.children_ul);
2476      }
2477      return node.children_ul;
2478    };
2479
2480  return node;
2481}
2482
2483
2484
2485
2486function expand_node(me, node)
2487{
2488  if (node.children_data && !node.expanded) {
2489    if (node.children_visited) {
2490      $(node.get_children_ul()).slideDown("fast");
2491    } else {
2492      get_node(me, node);
2493      if ($(node.label_div).hasClass("absent")) {
2494        $(node.get_children_ul()).addClass("absent");
2495      }
2496      $(node.get_children_ul()).slideDown("fast");
2497    }
2498    node.plus_img.src = me.toroot + "assets/images/triangle-opened-small.png";
2499    node.expanded = true;
2500
2501    // perform api level toggling because new nodes are new to the DOM
2502    var selectedLevel = $("#apiLevelSelector option:selected").val();
2503    toggleVisisbleApis(selectedLevel, "#side-nav");
2504  }
2505}
2506
2507function get_node(me, mom)
2508{
2509  mom.children_visited = true;
2510  for (var i in mom.children_data) {
2511    var node_data = mom.children_data[i];
2512    mom.children[i] = new_node(me, mom, node_data[0], node_data[1],
2513        node_data[2], node_data[3]);
2514  }
2515}
2516
2517function this_page_relative(toroot)
2518{
2519  var full = document.location.pathname;
2520  var file = "";
2521  if (toroot.substr(0, 1) == "/") {
2522    if (full.substr(0, toroot.length) == toroot) {
2523      return full.substr(toroot.length);
2524    } else {
2525      // the file isn't under toroot.  Fail.
2526      return null;
2527    }
2528  } else {
2529    if (toroot != "./") {
2530      toroot = "./" + toroot;
2531    }
2532    do {
2533      if (toroot.substr(toroot.length-3, 3) == "../" || toroot == "./") {
2534        var pos = full.lastIndexOf("/");
2535        file = full.substr(pos) + file;
2536        full = full.substr(0, pos);
2537        toroot = toroot.substr(0, toroot.length-3);
2538      }
2539    } while (toroot != "" && toroot != "/");
2540    return file.substr(1);
2541  }
2542}
2543
2544function find_page(url, data)
2545{
2546  var nodes = data;
2547  var result = null;
2548  for (var i in nodes) {
2549    var d = nodes[i];
2550    if (d[1] == url) {
2551      return new Array(i);
2552    }
2553    else if (d[2] != null) {
2554      result = find_page(url, d[2]);
2555      if (result != null) {
2556        return (new Array(i).concat(result));
2557      }
2558    }
2559  }
2560  return null;
2561}
2562
2563function init_default_navtree(toroot) {
2564  // load json file for navtree data
2565  $.getScript(toRoot + 'navtree_data.js', function(data, textStatus, jqxhr) {
2566      // when the file is loaded, initialize the tree
2567      if(jqxhr.status === 200) {
2568          init_navtree("tree-list", toroot, NAVTREE_DATA);
2569      }
2570  });
2571
2572  // perform api level toggling because because the whole tree is new to the DOM
2573  var selectedLevel = $("#apiLevelSelector option:selected").val();
2574  toggleVisisbleApis(selectedLevel, "#side-nav");
2575}
2576
2577function init_navtree(navtree_id, toroot, root_nodes)
2578{
2579  var me = new Object();
2580  me.toroot = toroot;
2581  me.node = new Object();
2582
2583  me.node.li = document.getElementById(navtree_id);
2584  me.node.children_data = root_nodes;
2585  me.node.children = new Array();
2586  me.node.children_ul = document.createElement("ul");
2587  me.node.get_children_ul = function() { return me.node.children_ul; };
2588  //me.node.children_ul.className = "children_ul";
2589  me.node.li.appendChild(me.node.children_ul);
2590  me.node.depth = 0;
2591
2592  get_node(me, me.node);
2593
2594  me.this_page = this_page_relative(toroot);
2595  me.breadcrumbs = find_page(me.this_page, root_nodes);
2596  if (me.breadcrumbs != null && me.breadcrumbs.length != 0) {
2597    var mom = me.node;
2598    for (var i in me.breadcrumbs) {
2599      var j = me.breadcrumbs[i];
2600      mom = mom.children[j];
2601      expand_node(me, mom);
2602    }
2603    mom.label_div.className = mom.label_div.className + " selected";
2604    addLoadEvent(function() {
2605      scrollIntoView("nav-tree");
2606      });
2607  }
2608}
2609
2610/* TODO: eliminate redundancy with non-google functions */
2611function init_google_navtree(navtree_id, toroot, root_nodes)
2612{
2613  var me = new Object();
2614  me.toroot = toroot;
2615  me.node = new Object();
2616
2617  me.node.li = document.getElementById(navtree_id);
2618  me.node.children_data = root_nodes;
2619  me.node.children = new Array();
2620  me.node.children_ul = document.createElement("ul");
2621  me.node.get_children_ul = function() { return me.node.children_ul; };
2622  //me.node.children_ul.className = "children_ul";
2623  me.node.li.appendChild(me.node.children_ul);
2624  me.node.depth = 0;
2625
2626  get_google_node(me, me.node);
2627}
2628
2629function new_google_node(me, mom, text, link, children_data, api_level)
2630{
2631  var node = new Object();
2632  var child;
2633  node.children = Array();
2634  node.children_data = children_data;
2635  node.depth = mom.depth + 1;
2636  node.get_children_ul = function() {
2637      if (!node.children_ul) {
2638        node.children_ul = document.createElement("ul");
2639        node.children_ul.className = "tree-list-children";
2640        node.li.appendChild(node.children_ul);
2641      }
2642      return node.children_ul;
2643    };
2644  node.li = document.createElement("li");
2645
2646  mom.get_children_ul().appendChild(node.li);
2647
2648
2649  if(link) {
2650    child = document.createElement("a");
2651
2652  }
2653  else {
2654    child = document.createElement("span");
2655    child.className = "tree-list-subtitle";
2656
2657  }
2658  if (children_data != null) {
2659    node.li.className="nav-section";
2660    node.label_div = document.createElement("div");
2661    node.label_div.className = "nav-section-header-ref";
2662    node.li.appendChild(node.label_div);
2663    get_google_node(me, node);
2664    node.label_div.appendChild(child);
2665  }
2666  else {
2667    node.li.appendChild(child);
2668  }
2669  if(link) {
2670    child.href = me.toroot + link;
2671  }
2672  node.label = document.createTextNode(text);
2673  child.appendChild(node.label);
2674
2675  node.children_ul = null;
2676
2677  return node;
2678}
2679
2680function get_google_node(me, mom)
2681{
2682  mom.children_visited = true;
2683  var linkText;
2684  for (var i in mom.children_data) {
2685    var node_data = mom.children_data[i];
2686    linkText = node_data[0];
2687
2688    if(linkText.match("^"+"com.google.android")=="com.google.android"){
2689      linkText = linkText.substr(19, linkText.length);
2690    }
2691      mom.children[i] = new_google_node(me, mom, linkText, node_data[1],
2692          node_data[2], node_data[3]);
2693  }
2694}
2695function showGoogleRefTree() {
2696  init_default_google_navtree(toRoot);
2697  init_default_gcm_navtree(toRoot);
2698}
2699
2700function init_default_google_navtree(toroot) {
2701  // load json file for navtree data
2702  $.getScript(toRoot + 'gms_navtree_data.js', function(data, textStatus, jqxhr) {
2703      // when the file is loaded, initialize the tree
2704      if(jqxhr.status === 200) {
2705          init_google_navtree("gms-tree-list", toroot, GMS_NAVTREE_DATA);
2706          highlightSidenav();
2707          resizeNav();
2708      }
2709  });
2710}
2711
2712function init_default_gcm_navtree(toroot) {
2713  // load json file for navtree data
2714  $.getScript(toRoot + 'gcm_navtree_data.js', function(data, textStatus, jqxhr) {
2715      // when the file is loaded, initialize the tree
2716      if(jqxhr.status === 200) {
2717          init_google_navtree("gcm-tree-list", toroot, GCM_NAVTREE_DATA);
2718          highlightSidenav();
2719          resizeNav();
2720      }
2721  });
2722}
2723
2724/* TOGGLE INHERITED MEMBERS */
2725
2726/* Toggle an inherited class (arrow toggle)
2727 * @param linkObj  The link that was clicked.
2728 * @param expand  'true' to ensure it's expanded. 'false' to ensure it's closed.
2729 *                'null' to simply toggle.
2730 */
2731function toggleInherited(linkObj, expand) {
2732    var base = linkObj.getAttribute("id");
2733    var list = document.getElementById(base + "-list");
2734    var summary = document.getElementById(base + "-summary");
2735    var trigger = document.getElementById(base + "-trigger");
2736    var a = $(linkObj);
2737    if ( (expand == null && a.hasClass("closed")) || expand ) {
2738        list.style.display = "none";
2739        summary.style.display = "block";
2740        trigger.src = toRoot + "assets/images/triangle-opened.png";
2741        a.removeClass("closed");
2742        a.addClass("opened");
2743    } else if ( (expand == null && a.hasClass("opened")) || (expand == false) ) {
2744        list.style.display = "block";
2745        summary.style.display = "none";
2746        trigger.src = toRoot + "assets/images/triangle-closed.png";
2747        a.removeClass("opened");
2748        a.addClass("closed");
2749    }
2750    return false;
2751}
2752
2753/* Toggle all inherited classes in a single table (e.g. all inherited methods)
2754 * @param linkObj  The link that was clicked.
2755 * @param expand  'true' to ensure it's expanded. 'false' to ensure it's closed.
2756 *                'null' to simply toggle.
2757 */
2758function toggleAllInherited(linkObj, expand) {
2759  var a = $(linkObj);
2760  var table = $(a.parent().parent().parent()); // ugly way to get table/tbody
2761  var expandos = $(".jd-expando-trigger", table);
2762  if ( (expand == null && a.text() == "[Expand]") || expand ) {
2763    expandos.each(function(i) {
2764      toggleInherited(this, true);
2765    });
2766    a.text("[Collapse]");
2767  } else if ( (expand == null && a.text() == "[Collapse]") || (expand == false) ) {
2768    expandos.each(function(i) {
2769      toggleInherited(this, false);
2770    });
2771    a.text("[Expand]");
2772  }
2773  return false;
2774}
2775
2776/* Toggle all inherited members in the class (link in the class title)
2777 */
2778function toggleAllClassInherited() {
2779  var a = $("#toggleAllClassInherited"); // get toggle link from class title
2780  var toggles = $(".toggle-all", $("#body-content"));
2781  if (a.text() == "[Expand All]") {
2782    toggles.each(function(i) {
2783      toggleAllInherited(this, true);
2784    });
2785    a.text("[Collapse All]");
2786  } else {
2787    toggles.each(function(i) {
2788      toggleAllInherited(this, false);
2789    });
2790    a.text("[Expand All]");
2791  }
2792  return false;
2793}
2794
2795/* Expand all inherited members in the class. Used when initiating page search */
2796function ensureAllInheritedExpanded() {
2797  var toggles = $(".toggle-all", $("#body-content"));
2798  toggles.each(function(i) {
2799    toggleAllInherited(this, true);
2800  });
2801  $("#toggleAllClassInherited").text("[Collapse All]");
2802}
2803
2804
2805/* HANDLE KEY EVENTS
2806 * - Listen for Ctrl+F (Cmd on Mac) and expand all inherited members (to aid page search)
2807 */
2808var agent = navigator['userAgent'].toLowerCase();
2809var mac = agent.indexOf("macintosh") != -1;
2810
2811$(document).keydown( function(e) {
2812var control = mac ? e.metaKey && !e.ctrlKey : e.ctrlKey; // get ctrl key
2813  if (control && e.which == 70) {  // 70 is "F"
2814    ensureAllInheritedExpanded();
2815  }
2816});
2817