docs.js revision aa2ce5d9aa16d72e4f5c4203bc9a8c6f87d83719
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
9
10var basePath = getBaseUri(location.pathname);
11var SITE_ROOT = toRoot + basePath.substring(1,basePath.indexOf("/",1));
12
13
14/******  ON LOAD SET UP STUFF *********/
15
16var navBarIsFixed = false;
17$(document).ready(function() {
18  // init the fullscreen toggle click event
19  $('#nav-swap .fullscreen').click(function(){
20    if ($(this).hasClass('disabled')) {
21      toggleFullscreen(true);
22    } else {
23      toggleFullscreen(false);
24    }
25  });
26
27  // initialize the divs with custom scrollbars
28  $('.scroll-pane').jScrollPane( {verticalGutter:0} );
29
30  // add HRs below all H2s (except for a few other h2 variants)
31  $('h2').not('#qv h2').not('#tb h2').not('.sidebox h2').not('#devdoc-nav h2').css({marginBottom:0}).after('<hr/>');
32
33  // set search's onkeyup handler here so we can show suggestions
34  // even while search results are visible
35  $("#search_autocomplete").keyup(function() {return search_changed(event, false, toRoot)});
36
37  // set up the search close button
38  $('.search .close').click(function() {
39    $searchInput = $('#search_autocomplete');
40    $searchInput.attr('value', '');
41    $(this).addClass("hide");
42    $("#search-container").removeClass('active');
43    $("#search_autocomplete").blur();
44    search_focus_changed($searchInput.get(), false);  // see search_autocomplete.js
45    hideResults();  // see search_autocomplete.js
46  });
47  $('.search').click(function() {
48    if (!$('#search_autocomplete').is(":focused")) {
49        $('#search_autocomplete').focus();
50    }
51  });
52
53  // Set up quicknav
54  var quicknav_open = false;
55  $("#btn-quicknav").click(function() {
56    if (quicknav_open) {
57      $(this).removeClass('active');
58      quicknav_open = false;
59      collapse();
60    } else {
61      $(this).addClass('active');
62      quicknav_open = true;
63      expand();
64    }
65  })
66
67  var expand = function() {
68   $('#header-wrap').addClass('quicknav');
69   $('#quicknav').stop().show().animate({opacity:'1'});
70  }
71
72  var collapse = function() {
73    $('#quicknav').stop().animate({opacity:'0'}, 100, function() {
74      $(this).hide();
75      $('#header-wrap').removeClass('quicknav');
76    });
77  }
78
79
80  //Set up search
81  $("#search_autocomplete").focus(function() {
82    $("#search-container").addClass('active');
83  })
84  $("#search-container").mouseover(function() {
85    $("#search-container").addClass('active');
86    $("#search_autocomplete").focus();
87  })
88  $("#search-container").mouseout(function() {
89    if ($("#search_autocomplete").is(":focus")) return;
90    if ($("#search_autocomplete").val() == '') {
91      setTimeout(function(){
92        $("#search-container").removeClass('active');
93        $("#search_autocomplete").blur();
94      },250);
95    }
96  })
97  $("#search_autocomplete").blur(function() {
98    if ($("#search_autocomplete").val() == '') {
99      $("#search-container").removeClass('active');
100    }
101  })
102
103
104  // prep nav expandos
105  var pagePath = document.location.pathname;
106  // account for intl docs by removing the intl/*/ path
107  if (pagePath.indexOf("/intl/") == 0) {
108    pagePath = pagePath.substr(pagePath.indexOf("/",6)); // start after intl/ to get last /
109  }
110
111  if (pagePath.indexOf(SITE_ROOT) == 0) {
112    if (pagePath == '' || pagePath.charAt(pagePath.length - 1) == '/') {
113      pagePath += 'index.html';
114    }
115  }
116
117  if (SITE_ROOT.match(/\.\.\//) || SITE_ROOT == '') {
118    // If running locally, SITE_ROOT will be a relative path, so account for that by
119    // finding the relative URL to this page. This will allow us to find links on the page
120    // leading back to this page.
121    var pathParts = pagePath.split('/');
122    var relativePagePathParts = [];
123    var upDirs = (SITE_ROOT.match(/(\.\.\/)+/) || [''])[0].length / 3;
124    for (var i = 0; i < upDirs; i++) {
125      relativePagePathParts.push('..');
126    }
127    for (var i = 0; i < upDirs; i++) {
128      relativePagePathParts.push(pathParts[pathParts.length - (upDirs - i) - 1]);
129    }
130    relativePagePathParts.push(pathParts[pathParts.length - 1]);
131    pagePath = relativePagePathParts.join('/');
132  } else {
133    // Otherwise the page path is already an absolute URL
134  }
135
136  // select current page in sidenav and set up prev/next links if they exist
137  var $selNavLink = $('#nav').find('a[href="' + pagePath + '"]');
138  if ($selNavLink.length) {
139    $selListItem = $selNavLink.closest('li');
140
141    $selListItem.addClass('selected');
142    $selListItem.closest('li.nav-section').addClass('expanded');
143    $selListItem.closest('li.nav-section').children('ul').show();
144    $selListItem.closest('li.nav-section').parent().closest('li.nav-section').addClass('expanded');
145    $selListItem.closest('li.nav-section').parent().closest('ul').show();
146
147
148  //  $selListItem.closest('li.nav-section').closest('li.nav-section').addClass('expanded');
149  //  $selListItem.closest('li.nav-section').closest('li.nav-section').children('ul').show();
150
151    // set up prev links
152    var $prevLink = [];
153    var $prevListItem = $selListItem.prev('li');
154
155    var crossBoundaries = ($("body.design").length > 0) || ($("body.guide").length > 0) ? true :
156false; // navigate across topic boundaries only in design docs
157    if ($prevListItem.length) {
158      if ($prevListItem.hasClass('nav-section')) {
159        if (crossBoundaries) {
160          // jump to last topic of previous section
161          $prevLink = $prevListItem.find('a:last');
162        }
163      } else {
164        // jump to previous topic in this section
165        $prevLink = $prevListItem.find('a:eq(0)');
166      }
167    } else {
168      // jump to this section's index page (if it exists)
169      var $parentListItem = $selListItem.parents('li');
170      $prevLink = $selListItem.parents('li').find('a');
171
172      // except if cross boundaries aren't allowed, and we're at the top of a section already
173      // (and there's another parent)
174      if (!crossBoundaries && $parentListItem.hasClass('nav-section')
175                           && $selListItem.hasClass('nav-section')) {
176        $prevLink = [];
177      }
178    }
179
180    if ($prevLink.length) {
181      var prevHref = $prevLink.attr('href');
182      if (prevHref == SITE_ROOT + 'index.html') {
183        // Don't show Previous when it leads to the homepage
184      } else {
185        $('.prev-page-link').attr('href', $prevLink.attr('href')).removeClass("hide");
186      }
187    }
188
189    // set up next links
190    var $nextLink = [];
191    var startCourse = false;
192    var startClass = false;
193    var training = $(".next-class-link").length; // decides whether to provide "next class" link
194    var isCrossingBoundary = false;
195
196    if ($selListItem.hasClass('nav-section')) {
197      // we're on an index page, jump to the first topic
198      $nextLink = $selListItem.find('ul:eq(0)').find('a:eq(0)');
199
200      // if there aren't any children, go to the next section (required for About pages)
201      if($nextLink.length == 0) {
202        $nextLink = $selListItem.next('li').find('a');
203      } else if ($('.topic-start-link').length) {
204        // as long as there's a child link and there is a "topic start link" (we're on a landing)
205        // then set the landing page "start link" text to be the first doc title
206        $('.topic-start-link').text($nextLink.text().toUpperCase());
207      }
208
209      // Handle some Training specialties
210      if ($selListItem.parent().is("#nav") && $(".start-course-link").length) {
211        // this means we're at the very top of the TOC hierarchy
212        startCourse = true;
213      } else if ($(".start-class-link").length) {
214        // this means this page has children but is not at the top (it's a class, not a course)
215        startClass = true;
216      }
217    } else {
218      // jump to the next topic in this section (if it exists)
219      $nextLink = $selListItem.next('li').find('a:eq(0)');
220      if (!$nextLink.length) {
221        if (crossBoundaries || training) {
222          // no more topics in this section, jump to the first topic in the next section
223          $nextLink = $selListItem.parents('li:eq(0)').next('li.nav-section').find('a:eq(0)');
224          isCrossingBoundary = true;
225        }
226      }
227    }
228    if ($nextLink.length) {
229      if (startCourse || startClass) {
230        if (startCourse) {
231          $('.start-course-link').attr('href', $nextLink.attr('href')).removeClass("hide");
232        } else {
233          $('.start-class-link').attr('href', $nextLink.attr('href')).removeClass("hide");
234        }
235        // if there's no training bar (below the start button),
236        // then we need to add a bottom border to button
237        if (!$("#tb").length) {
238          $('.start-course-link').css({'border-bottom':'1px solid #DADADA'});
239          $('.start-class-link').css({'border-bottom':'1px solid #DADADA'});
240        }
241      } else if (training && isCrossingBoundary) {
242        $('.content-footer.next-class').show();
243        $('.next-page-link').attr('href','')
244                            .removeClass("hide").addClass("disabled")
245                            .click(function() { return false; });
246
247        $('.next-class-link').attr('href',$nextLink.attr('href'))
248                            .removeClass("hide").append($nextLink.html());
249        $('.next-class-link').find('.new').empty();
250      } else {
251        $('.next-page-link').attr('href', $nextLink.attr('href')).removeClass("hide");
252      }
253    }
254
255  }
256
257
258
259  // Set up expand/collapse behavior
260  $('#nav li.nav-section .nav-section-header').click(function() {
261    var section = $(this).closest('li.nav-section');
262    if (section.hasClass('expanded')) {
263    /* hide me */
264    //  if (section.hasClass('selected') || section.find('li').hasClass('selected')) {
265   //   /* but not if myself or my descendents are selected */
266   //     return;
267    //  }
268      section.children('ul').slideUp(250, function() {
269        section.closest('li').removeClass('expanded');
270        resizeNav();
271      });
272    } else {
273    /* show me */
274      // first hide all other siblings
275      var $others = $('li.nav-section.expanded', $(this).closest('ul'));
276      $others.removeClass('expanded').children('ul').slideUp(250);
277
278      // now expand me
279      section.closest('li').addClass('expanded');
280      section.children('ul').slideDown(250, function() {
281        resizeNav();
282      });
283    }
284  });
285
286  $(".scroll-pane").scroll(function(event) {
287      event.preventDefault();
288      return false;
289  });
290
291  /* Resize nav height when window height changes */
292  $(window).resize(function() {
293    if ($('#side-nav').length == 0) return;
294    var stylesheet = $('link[rel="stylesheet"][class="fullscreen"]');
295    setNavBarLeftPos(); // do this even if sidenav isn't fixed because it could become fixed
296    // make sidenav behave when resizing the window and side-scolling is a concern
297    if (navBarIsFixed) {
298      if ((stylesheet.attr("disabled") == "disabled") || stylesheet.length == 0) {
299        updateSideNavPosition();
300      } else {
301        updateSidenavFullscreenWidth();
302      }
303    }
304    resizeNav();
305  });
306
307
308  // Set up fixed navbar
309  var prevScrollLeft = 0; // used to compare current position to previous position of horiz scroll
310  $(window).scroll(function(event) {
311    if ($('#side-nav').length == 0) return;
312    if (event.target.nodeName == "DIV") {
313      // Dump scroll event if the target is a DIV, because that means the event is coming
314      // from a scrollable div and so there's no need to make adjustments to our layout
315      return;
316    }
317    var scrollTop = $(window).scrollTop();
318    var headerHeight = $('#header').outerHeight();
319    var subheaderHeight = $('#nav-x').outerHeight();
320    var searchResultHeight = $('#searchResults').is(":visible") ?
321                             $('#searchResults').outerHeight() : 0;
322    var totalHeaderHeight = headerHeight + subheaderHeight + searchResultHeight;
323    var navBarShouldBeFixed = scrollTop > totalHeaderHeight;
324
325    var scrollLeft = $(window).scrollLeft();
326    // When the sidenav is fixed and user scrolls horizontally, reposition the sidenav to match
327    if (navBarIsFixed && (scrollLeft != prevScrollLeft)) {
328      updateSideNavPosition();
329      prevScrollLeft = scrollLeft;
330    }
331
332    // Don't continue if the header is sufficently far away
333    // (to avoid intensive resizing that slows scrolling)
334    if (navBarIsFixed && navBarShouldBeFixed) {
335      return;
336    }
337
338    if (navBarIsFixed != navBarShouldBeFixed) {
339      if (navBarShouldBeFixed) {
340        // make it fixed
341        var width = $('#devdoc-nav').width();
342        $('#devdoc-nav')
343            .addClass('fixed')
344            .css({'width':width+'px'})
345            .prependTo('#body-content');
346        // add neato "back to top" button
347        $('#devdoc-nav a.totop').css({'display':'block','width':$("#nav").innerWidth()+'px'});
348
349        // update the sidenaav position for side scrolling
350        updateSideNavPosition();
351      } else {
352        // make it static again
353        $('#devdoc-nav')
354            .removeClass('fixed')
355            .css({'width':'auto','margin':''})
356            .prependTo('#side-nav');
357        $('#devdoc-nav a.totop').hide();
358      }
359      navBarIsFixed = navBarShouldBeFixed;
360    }
361
362    resizeNav(250); // pass true in order to delay the scrollbar re-initialization for performance
363  });
364
365
366  var navBarLeftPos;
367  if ($('#devdoc-nav').length) {
368    setNavBarLeftPos();
369  }
370
371
372  // Stop expand/collapse behavior when clicking on nav section links (since we're navigating away
373  // from the page)
374  $('.nav-section-header').find('a:eq(0)').click(function(evt) {
375    window.location.href = $(this).attr('href');
376    return false;
377  });
378
379  // Set up play-on-hover <video> tags.
380  $('video.play-on-hover').bind('click', function(){
381    $(this).get(0).load(); // in case the video isn't seekable
382    $(this).get(0).play();
383  });
384
385  // Set up tooltips
386  var TOOLTIP_MARGIN = 10;
387  $('acronym').each(function() {
388    var $target = $(this);
389    var $tooltip = $('<div>')
390        .addClass('tooltip-box')
391        .text($target.attr('title'))
392        .hide()
393        .appendTo('body');
394    $target.removeAttr('title');
395
396    $target.hover(function() {
397      // in
398      var targetRect = $target.offset();
399      targetRect.width = $target.width();
400      targetRect.height = $target.height();
401
402      $tooltip.css({
403        left: targetRect.left,
404        top: targetRect.top + targetRect.height + TOOLTIP_MARGIN
405      });
406      $tooltip.addClass('below');
407      $tooltip.show();
408    }, function() {
409      // out
410      $tooltip.hide();
411    });
412  });
413
414  // Set up <h2> deeplinks
415  $('h2').click(function() {
416    var id = $(this).attr('id');
417    if (id) {
418      document.location.hash = id;
419    }
420  });
421
422  //Loads the +1 button
423  var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
424  po.src = 'https://apis.google.com/js/plusone.js';
425  var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
426
427
428  // Revise the sidenav widths to make room for the scrollbar
429  // which avoids the visible width from changing each time the bar appears
430  var $sidenav = $("#side-nav");
431  var sidenav_width = parseInt($sidenav.innerWidth());
432
433  $("#devdoc-nav  #nav").css("width", sidenav_width - 4 + "px"); // 4px is scrollbar width
434
435
436  $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller
437
438  if ($(".scroll-pane").length > 1) {
439    // Check if there's a user preference for the panel heights
440    var cookieHeight = readCookie("reference_height");
441    if (cookieHeight) {
442      restoreHeight(cookieHeight);
443    }
444  }
445
446  resizeNav();
447
448
449});
450
451
452
453function toggleFullscreen(enable) {
454  var delay = 20;
455  var enabled = true;
456  var stylesheet = $('link[rel="stylesheet"][class="fullscreen"]');
457  if (enable) {
458    // Currently NOT USING fullscreen; enable fullscreen
459    stylesheet.removeAttr('disabled');
460    $('#nav-swap .fullscreen').removeClass('disabled');
461    $('#devdoc-nav').css({left:''});
462    setTimeout(updateSidenavFullscreenWidth,delay); // need to wait a moment for css to switch
463    enabled = true;
464  } else {
465    // Currently USING fullscreen; disable fullscreen
466    stylesheet.attr('disabled', 'disabled');
467    $('#nav-swap .fullscreen').addClass('disabled');
468    setTimeout(updateSidenavFixedWidth,delay); // need to wait a moment for css to switch
469    enabled = false;
470  }
471  writeCookie("fullscreen", enabled, null, null);
472  setNavBarLeftPos();
473  resizeNav(delay);
474  updateSideNavPosition();
475  setTimeout(initSidenavHeightResize,delay);
476}
477
478
479function setNavBarLeftPos() {
480  navBarLeftPos = $('#body-content').offset().left;
481}
482
483
484function updateSideNavPosition() {
485  var newLeft = $(window).scrollLeft() - navBarLeftPos;
486  $('#devdoc-nav').css({left: -newLeft});
487  $('#devdoc-nav .totop').css({left: -(newLeft - parseInt($('#side-nav').css('margin-left')))});
488}
489
490
491
492
493
494
495
496
497// TODO: use $(document).ready instead
498function addLoadEvent(newfun) {
499  var current = window.onload;
500  if (typeof window.onload != 'function') {
501    window.onload = newfun;
502  } else {
503    window.onload = function() {
504      current();
505      newfun();
506    }
507  }
508}
509
510var agent = navigator['userAgent'].toLowerCase();
511// If a mobile phone, set flag and do mobile setup
512if ((agent.indexOf("mobile") != -1) ||      // android, iphone, ipod
513    (agent.indexOf("blackberry") != -1) ||
514    (agent.indexOf("webos") != -1) ||
515    (agent.indexOf("mini") != -1)) {        // opera mini browsers
516  isMobile = true;
517}
518
519
520/* loads the lists.js file to the page.
521Loading this in the head was slowing page load time */
522addLoadEvent( function() {
523  var lists = document.createElement("script");
524  lists.setAttribute("type","text/javascript");
525  lists.setAttribute("src", toRoot+"reference/lists.js");
526  document.getElementsByTagName("head")[0].appendChild(lists);
527} );
528
529
530addLoadEvent( function() {
531  $("pre:not(.no-pretty-print)").addClass("prettyprint");
532  prettyPrint();
533} );
534
535function init() {
536  //resizeNav();
537
538  resizePackagesNav = $("#resize-packages-nav");
539  classesNav = $("#classes-nav");
540  devdocNav = $("#devdoc-nav");
541
542  var cookiePath = "";
543  if (location.href.indexOf("/reference/") != -1) {
544    cookiePath = "reference_";
545  } else if (location.href.indexOf("/guide/") != -1) {
546    cookiePath = "guide_";
547  } else if (location.href.indexOf("/tools/") != -1) {
548    cookiePath = "tools_";
549  } else if (location.href.indexOf("/training/") != -1) {
550    cookiePath = "training_";
551  } else if (location.href.indexOf("/design/") != -1) {
552    cookiePath = "design_";
553  } else if (location.href.indexOf("/distribute/") != -1) {
554    cookiePath = "distribute_";
555  }
556}
557
558
559
560/* ######### RESIZE THE SIDENAV HEIGHT ########## */
561
562function resizeNav(delay) {
563  var $nav = $("#devdoc-nav");
564  var $window = $(window);
565  var navHeight;
566
567  // Get the height of entire window and the total header height.
568  // Then figure out based on scroll position whether the header is visible
569  var windowHeight = $window.height();
570  var scrollTop = $window.scrollTop();
571  var headerHeight = $('#header').outerHeight();
572  var subheaderHeight = $('#nav-x').outerHeight();
573  var headerVisible = (scrollTop < (headerHeight + subheaderHeight));
574
575  // get the height of space between nav and top of window.
576  // Could be either margin or top position, depending on whether the nav is fixed.
577  var topMargin = (parseInt($nav.css('margin-top')) || parseInt($nav.css('top'))) + 1;
578  // add 1 for the #side-nav bottom margin
579
580  // Depending on whether the header is visible, set the side nav's height.
581  if (headerVisible) {
582    // The sidenav height grows as the header goes off screen
583    navHeight = windowHeight - (headerHeight + subheaderHeight - scrollTop) - topMargin;
584  } else {
585    // Once header is off screen, the nav height is almost full window height
586    navHeight = windowHeight - topMargin;
587  }
588
589
590
591  $scrollPanes = $(".scroll-pane");
592  if ($scrollPanes.length > 1) {
593    // subtract the height of the api level widget and nav swapper from the available nav height
594    navHeight -= ($('#api-nav-header').outerHeight(true) + $('#nav-swap').outerHeight(true));
595
596    $("#swapper").css({height:navHeight + "px"});
597    if ($("#nav-tree").is(":visible")) {
598      $("#nav-tree").css({height:navHeight});
599    }
600
601    var classesHeight = navHeight - parseInt($("#resize-packages-nav").css("height")) - 10 + "px";
602    //subtract 10px to account for drag bar
603
604    // if the window becomes small enough to make the class panel height 0,
605    // then the package panel should begin to shrink
606    if (parseInt(classesHeight) <= 0) {
607      $("#resize-packages-nav").css({height:navHeight - 10}); //subtract 10px for drag bar
608      $("#packages-nav").css({height:navHeight - 10});
609    }
610
611    $("#classes-nav").css({'height':classesHeight, 'margin-top':'10px'});
612    $("#classes-nav .jspContainer").css({height:classesHeight});
613
614
615  } else {
616    $nav.height(navHeight);
617  }
618
619  if (delay) {
620    updateFromResize = true;
621    delayedReInitScrollbars(delay);
622  } else {
623    reInitScrollbars();
624  }
625
626}
627
628var updateScrollbars = false;
629var updateFromResize = false;
630
631/* Re-initialize the scrollbars to account for changed nav size.
632 * This method postpones the actual update by a 1/4 second in order to optimize the
633 * scroll performance while the header is still visible, because re-initializing the
634 * scroll panes is an intensive process.
635 */
636function delayedReInitScrollbars(delay) {
637  // If we're scheduled for an update, but have received another resize request
638  // before the scheduled resize has occured, just ignore the new request
639  // (and wait for the scheduled one).
640  if (updateScrollbars && updateFromResize) {
641    updateFromResize = false;
642    return;
643  }
644
645  // We're scheduled for an update and the update request came from this method's setTimeout
646  if (updateScrollbars && !updateFromResize) {
647    reInitScrollbars();
648    updateScrollbars = false;
649  } else {
650    updateScrollbars = true;
651    updateFromResize = false;
652    setTimeout('delayedReInitScrollbars()',delay);
653  }
654}
655
656/* Re-initialize the scrollbars to account for changed nav size. */
657function reInitScrollbars() {
658  var pane = $(".scroll-pane").each(function(){
659    var api = $(this).data('jsp');
660    if (!api) { setTimeout(reInitScrollbars,300); return;}
661    api.reinitialise( {verticalGutter:0} );
662  });
663  $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller
664}
665
666
667/* Resize the height of the nav panels in the reference,
668 * and save the new size to a cookie */
669function saveNavPanels() {
670  var basePath = getBaseUri(location.pathname);
671  var section = basePath.substring(1,basePath.indexOf("/",1));
672  writeCookie("height", resizePackagesNav.css("height"), section, null);
673}
674
675
676
677function restoreHeight(packageHeight) {
678    $("#resize-packages-nav").height(packageHeight);
679    $("#packages-nav").height(packageHeight);
680  //  var classesHeight = navHeight - packageHeight;
681 //   $("#classes-nav").css({height:classesHeight});
682  //  $("#classes-nav .jspContainer").css({height:classesHeight});
683}
684
685
686
687/* ######### END RESIZE THE SIDENAV HEIGHT ########## */
688
689
690
691
692
693/** Scroll the jScrollPane to make the currently selected item visible
694    This is called when the page finished loading. */
695function scrollIntoView(nav) {
696  var $nav = $("#"+nav);
697  var element = $nav.jScrollPane({/* ...settings... */});
698  var api = element.data('jsp');
699
700  if ($nav.is(':visible')) {
701    var $selected = $(".selected", $nav);
702    if ($selected.length == 0) return;
703
704    var selectedOffset = $selected.position().top;
705    if (selectedOffset + 90 > $nav.height()) {  // add 90 so that we scroll up even
706                                                // if the current item is close to the bottom
707      api.scrollTo(0, selectedOffset - ($nav.height() / 4), false); // scroll the item into view
708                                                              // to be 1/4 of the way from the top
709    }
710  }
711}
712
713
714
715
716
717
718/* Show popup dialogs */
719function showDialog(id) {
720  $dialog = $("#"+id);
721  $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>');
722  $dialog.wrapInner('<div/>');
723  $dialog.removeClass("hide");
724}
725
726
727
728
729
730/* #########    COOKIES!     ########## */
731
732function readCookie(cookie) {
733  var myCookie = cookie_namespace+"_"+cookie+"=";
734  if (document.cookie) {
735    var index = document.cookie.indexOf(myCookie);
736    if (index != -1) {
737      var valStart = index + myCookie.length;
738      var valEnd = document.cookie.indexOf(";", valStart);
739      if (valEnd == -1) {
740        valEnd = document.cookie.length;
741      }
742      var val = document.cookie.substring(valStart, valEnd);
743      return val;
744    }
745  }
746  return 0;
747}
748
749function writeCookie(cookie, val, section, expiration) {
750  if (val==undefined) return;
751  section = section == null ? "_" : "_"+section+"_";
752  if (expiration == null) {
753    var date = new Date();
754    date.setTime(date.getTime()+(10*365*24*60*60*1000)); // default expiration is one week
755    expiration = date.toGMTString();
756  }
757  var cookieValue = cookie_namespace + section + cookie + "=" + val
758                    + "; expires=" + expiration+"; path=/";
759  document.cookie = cookieValue;
760}
761
762/* #########     END COOKIES!     ########## */
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788/*
789
790REMEMBER THE PREVIOUS PAGE FOR EACH TAB
791
792function loadLast(cookiePath) {
793  var location = window.location.href;
794  if (location.indexOf("/"+cookiePath+"/") != -1) {
795    return true;
796  }
797  var lastPage = readCookie(cookiePath + "_lastpage");
798  if (lastPage) {
799    window.location = lastPage;
800    return false;
801  }
802  return true;
803}
804
805
806
807$(window).unload(function(){
808  var path = getBaseUri(location.pathname);
809  if (path.indexOf("/reference/") != -1) {
810    writeCookie("lastpage", path, "reference", null);
811  } else if (path.indexOf("/guide/") != -1) {
812    writeCookie("lastpage", path, "guide", null);
813  } else if ((path.indexOf("/resources/") != -1) || (path.indexOf("/training/") != -1)) {
814    writeCookie("lastpage", path, "resources", null);
815  }
816});
817
818*/
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833function toggle(obj, slide) {
834  var ul = $("ul:first", obj);
835  var li = ul.parent();
836  if (li.hasClass("closed")) {
837    if (slide) {
838      ul.slideDown("fast");
839    } else {
840      ul.show();
841    }
842    li.removeClass("closed");
843    li.addClass("open");
844    $(".toggle-img", li).attr("title", "hide pages");
845  } else {
846    ul.slideUp("fast");
847    li.removeClass("open");
848    li.addClass("closed");
849    $(".toggle-img", li).attr("title", "show pages");
850  }
851}
852
853
854
855
856
857function buildToggleLists() {
858  $(".toggle-list").each(
859    function(i) {
860      $("div:first", this).append("<a class='toggle-img' href='#' title='show pages' onClick='toggle(this.parentNode.parentNode, true); return false;'></a>");
861      $(this).addClass("closed");
862    });
863}
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896/*      REFERENCE NAV SWAP     */
897
898
899function getNavPref() {
900  var v = readCookie('reference_nav');
901  if (v != NAV_PREF_TREE) {
902    v = NAV_PREF_PANELS;
903  }
904  return v;
905}
906
907function chooseDefaultNav() {
908  nav_pref = getNavPref();
909  if (nav_pref == NAV_PREF_TREE) {
910    $("#nav-panels").toggle();
911    $("#panel-link").toggle();
912    $("#nav-tree").toggle();
913    $("#tree-link").toggle();
914  }
915}
916
917function swapNav() {
918  if (nav_pref == NAV_PREF_TREE) {
919    nav_pref = NAV_PREF_PANELS;
920  } else {
921    nav_pref = NAV_PREF_TREE;
922    init_default_navtree(toRoot);
923  }
924  var date = new Date();
925  date.setTime(date.getTime()+(10*365*24*60*60*1000)); // keep this for 10 years
926  writeCookie("nav", nav_pref, "reference", date.toGMTString());
927
928  $("#nav-panels").toggle();
929  $("#panel-link").toggle();
930  $("#nav-tree").toggle();
931  $("#tree-link").toggle();
932
933  resizeNav();
934
935  // Gross nasty hack to make tree view show up upon first swap by setting height manually
936  $("#nav-tree .jspContainer:visible")
937      .css({'height':$("#nav-tree .jspContainer .jspPane").height() +'px'});
938  // Another nasty hack to make the scrollbar appear now that we have height
939  resizeNav();
940
941  if ($("#nav-tree").is(':visible')) {
942    scrollIntoView("nav-tree");
943  } else {
944    scrollIntoView("packages-nav");
945    scrollIntoView("classes-nav");
946  }
947}
948
949
950
951/* ############################################ */
952/* ##########     LOCALIZATION     ############ */
953/* ############################################ */
954
955function getBaseUri(uri) {
956  var intlUrl = (uri.substring(0,6) == "/intl/");
957  if (intlUrl) {
958    base = uri.substring(uri.indexOf('intl/')+5,uri.length);
959    base = base.substring(base.indexOf('/')+1, base.length);
960      //alert("intl, returning base url: /" + base);
961    return ("/" + base);
962  } else {
963      //alert("not intl, returning uri as found.");
964    return uri;
965  }
966}
967
968function requestAppendHL(uri) {
969//append "?hl=<lang> to an outgoing request (such as to blog)
970  var lang = getLangPref();
971  if (lang) {
972    var q = 'hl=' + lang;
973    uri += '?' + q;
974    window.location = uri;
975    return false;
976  } else {
977    return true;
978  }
979}
980
981
982function changeTabLang(lang) {
983  var nodes = $("#header,#nav-x,.training-nav-top").find("."+lang);
984  for (i=0; i < nodes.length; i++) { // for each node in this language
985    var node = $(nodes[i]);
986    node.siblings().css("display","none"); // hide all siblings
987    if (node.not(":empty").length != 0) { //if this languages node has a translation, show it
988      node.css("display","inline");
989    } else { //otherwise, show English instead
990      node.css("display","none");
991      node.siblings().filter(".en").css("display","inline");
992    }
993  }
994}
995
996function changeNavLang(lang) {
997  var nodes = $("#devdoc-nav").find("."+lang);
998  for (i=0; i < nodes.length; i++) { // for each node in this language
999    var node = $(nodes[i]);
1000    node.siblings().css("display","none"); // hide all siblings
1001    if (node.not(":empty").length != 0) { // if this languages node has a translation, show it
1002      node.css("display","inline");
1003    } else { // otherwise, show English instead
1004      node.css("display","none");
1005      node.siblings().filter(".en").css("display","inline");
1006    }
1007  }
1008}
1009
1010function changeDocLang(lang) {
1011  changeTabLang(lang);
1012  changeNavLang(lang);
1013}
1014
1015function changeLangPref(lang, refresh) {
1016  var date = new Date();
1017  expires = date.toGMTString(date.setTime(date.getTime()+(10*365*24*60*60*1000)));
1018  // keep this for 50 years
1019  //alert("expires: " + expires)
1020  writeCookie("pref_lang", lang, null, expires);
1021  changeDocLang(lang);
1022  if (refresh) {
1023    l = getBaseUri(location.pathname);
1024    window.location = l;
1025  }
1026}
1027
1028function loadLangPref() {
1029  var lang = readCookie("pref_lang");
1030  if (lang != 0) {
1031    $("#language").find("option[value='"+lang+"']").attr("selected",true);
1032  }
1033}
1034
1035function getLangPref() {
1036  var lang = $("#language").find(":selected").attr("value");
1037  if (!lang) {
1038    lang = readCookie("pref_lang");
1039  }
1040  return (lang != 0) ? lang : 'en';
1041}
1042
1043/* ##########     END LOCALIZATION     ############ */
1044
1045
1046
1047
1048
1049
1050/* Used to hide and reveal supplemental content, such as long code samples.
1051   See the companion CSS in android-developer-docs.css */
1052function toggleContent(obj) {
1053  var div = $(obj.parentNode.parentNode);
1054  var toggleMe = $(".toggle-content-toggleme",div);
1055  if (div.hasClass("closed")) { // if it's closed, open it
1056    toggleMe.slideDown();
1057    $(".toggle-content-text", obj).toggle();
1058    div.removeClass("closed").addClass("open");
1059    $(".toggle-content-img", div).attr("title", "hide").attr("src", toRoot
1060                  + "assets/images/triangle-opened.png");
1061  } else { // if it's open, close it
1062    toggleMe.slideUp('fast', function() {  // Wait until the animation is done before closing arrow
1063      $(".toggle-content-text", obj).toggle();
1064      div.removeClass("open").addClass("closed");
1065      $(".toggle-content-img", div).attr("title", "show").attr("src", toRoot
1066                  + "assets/images/triangle-closed.png");
1067    });
1068  }
1069  return false;
1070}
1071
1072
1073
1074
1075
1076
1077/*
1078 *  Slideshow 1.0
1079 *  Used on /index.html and /develop/index.html for carousel
1080 *
1081 *  Sample usage:
1082 *  HTML -
1083 *  <div class="slideshow-container">
1084 *   <a href="" class="slideshow-prev">Prev</a>
1085 *   <a href="" class="slideshow-next">Next</a>
1086 *   <ul>
1087 *       <li class="item"><img src="images/marquee1.jpg"></li>
1088 *       <li class="item"><img src="images/marquee2.jpg"></li>
1089 *       <li class="item"><img src="images/marquee3.jpg"></li>
1090 *       <li class="item"><img src="images/marquee4.jpg"></li>
1091 *   </ul>
1092 *  </div>
1093 *
1094 *   <script type="text/javascript">
1095 *   $('.slideshow-container').dacSlideshow({
1096 *       auto: true,
1097 *       btnPrev: '.slideshow-prev',
1098 *       btnNext: '.slideshow-next'
1099 *   });
1100 *   </script>
1101 *
1102 *  Options:
1103 *  btnPrev:    optional identifier for previous button
1104 *  btnNext:    optional identifier for next button
1105 *  auto:       whether or not to auto-proceed
1106 *  speed:      animation speed
1107 *  autoTime:   time between auto-rotation
1108 *  easing:     easing function for transition
1109 *  start:      item to select by default
1110 *  scroll:     direction to scroll in
1111 *  pagination: whether or not to include dotted pagination
1112 *
1113 */
1114
1115 (function($) {
1116 $.fn.dacSlideshow = function(o) {
1117
1118     //Options - see above
1119     o = $.extend({
1120         btnPrev:   null,
1121         btnNext:   null,
1122         auto:      true,
1123         speed:     500,
1124         autoTime:  12000,
1125         easing:    null,
1126         start:     0,
1127         scroll:    1,
1128         pagination: true
1129
1130     }, o || {});
1131
1132     //Set up a carousel for each
1133     return this.each(function() {
1134
1135         var running = false;
1136         var animCss = o.vertical ? "top" : "left";
1137         var sizeCss = o.vertical ? "height" : "width";
1138         var div = $(this);
1139         var ul = $("ul", div);
1140         var tLi = $("li", ul);
1141         var tl = tLi.size();
1142         var timer = null;
1143
1144         var li = $("li", ul);
1145         var itemLength = li.size();
1146         var curr = o.start;
1147
1148         li.css({float: o.vertical ? "none" : "left"});
1149         ul.css({margin: "0", padding: "0", position: "relative", "list-style-type": "none", "z-index": "1"});
1150         div.css({position: "relative", "z-index": "2", left: "0px"});
1151
1152         var liSize = o.vertical ? height(li) : width(li);
1153         var ulSize = liSize * itemLength;
1154         var divSize = liSize;
1155
1156         li.css({width: li.width(), height: li.height()});
1157         ul.css(sizeCss, ulSize+"px").css(animCss, -(curr*liSize));
1158
1159         div.css(sizeCss, divSize+"px");
1160
1161         //Pagination
1162         if (o.pagination) {
1163             var pagination = $("<div class='pagination'></div>");
1164             var pag_ul = $("<ul></ul>");
1165             if (tl > 1) {
1166               for (var i=0;i<tl;i++) {
1167                    var li = $("<li>"+i+"</li>");
1168                    pag_ul.append(li);
1169                    if (i==o.start) li.addClass('active');
1170                        li.click(function() {
1171                        go(parseInt($(this).text()));
1172                    })
1173                }
1174                pagination.append(pag_ul);
1175                div.append(pagination);
1176             }
1177         }
1178
1179         //Previous button
1180         if(o.btnPrev)
1181             $(o.btnPrev).click(function(e) {
1182                 e.preventDefault();
1183                 return go(curr-o.scroll);
1184             });
1185
1186         //Next button
1187         if(o.btnNext)
1188             $(o.btnNext).click(function(e) {
1189                 e.preventDefault();
1190                 return go(curr+o.scroll);
1191             });
1192
1193         //Auto rotation
1194         if(o.auto) startRotateTimer();
1195
1196         function startRotateTimer() {
1197             clearInterval(timer);
1198             timer = setInterval(function() {
1199                  if (curr == tl-1) {
1200                    go(0);
1201                  } else {
1202                    go(curr+o.scroll);
1203                  }
1204              }, o.autoTime);
1205         }
1206
1207         //Go to an item
1208         function go(to) {
1209             if(!running) {
1210
1211                 if(to<0) {
1212                    to = itemLength-1;
1213                 } else if (to>itemLength-1) {
1214                    to = 0;
1215                 }
1216                 curr = to;
1217
1218                 running = true;
1219
1220                 ul.animate(
1221                     animCss == "left" ? { left: -(curr*liSize) } : { top: -(curr*liSize) } , o.speed, o.easing,
1222                     function() {
1223                         running = false;
1224                     }
1225                 );
1226
1227                 $(o.btnPrev + "," + o.btnNext).removeClass("disabled");
1228                 $( (curr-o.scroll<0 && o.btnPrev)
1229                     ||
1230                    (curr+o.scroll > itemLength && o.btnNext)
1231                     ||
1232                    []
1233                  ).addClass("disabled");
1234
1235
1236                 var nav_items = $('li', pagination);
1237                 nav_items.removeClass('active');
1238                 nav_items.eq(to).addClass('active');
1239
1240
1241             }
1242             if(o.auto) startRotateTimer();
1243             return false;
1244         };
1245     });
1246 };
1247
1248 function css(el, prop) {
1249     return parseInt($.css(el[0], prop)) || 0;
1250 };
1251 function width(el) {
1252     return  el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1253 };
1254 function height(el) {
1255     return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1256 };
1257
1258 })(jQuery);
1259
1260
1261/*
1262 *  dacSlideshow 1.0
1263 *  Used on develop/index.html for side-sliding tabs
1264 *
1265 *  Sample usage:
1266 *  HTML -
1267 *  <div class="slideshow-container">
1268 *   <a href="" class="slideshow-prev">Prev</a>
1269 *   <a href="" class="slideshow-next">Next</a>
1270 *   <ul>
1271 *       <li class="item"><img src="images/marquee1.jpg"></li>
1272 *       <li class="item"><img src="images/marquee2.jpg"></li>
1273 *       <li class="item"><img src="images/marquee3.jpg"></li>
1274 *       <li class="item"><img src="images/marquee4.jpg"></li>
1275 *   </ul>
1276 *  </div>
1277 *
1278 *   <script type="text/javascript">
1279 *   $('.slideshow-container').dacSlideshow({
1280 *       auto: true,
1281 *       btnPrev: '.slideshow-prev',
1282 *       btnNext: '.slideshow-next'
1283 *   });
1284 *   </script>
1285 *
1286 *  Options:
1287 *  btnPrev:    optional identifier for previous button
1288 *  btnNext:    optional identifier for next button
1289 *  auto:       whether or not to auto-proceed
1290 *  speed:      animation speed
1291 *  autoTime:   time between auto-rotation
1292 *  easing:     easing function for transition
1293 *  start:      item to select by default
1294 *  scroll:     direction to scroll in
1295 *  pagination: whether or not to include dotted pagination
1296 *
1297 */
1298 (function($) {
1299 $.fn.dacTabbedList = function(o) {
1300
1301     //Options - see above
1302     o = $.extend({
1303         speed : 250,
1304         easing: null,
1305         nav_id: null,
1306         frame_id: null
1307     }, o || {});
1308
1309     //Set up a carousel for each
1310     return this.each(function() {
1311
1312         var curr = 0;
1313         var running = false;
1314         var animCss = "margin-left";
1315         var sizeCss = "width";
1316         var div = $(this);
1317
1318         var nav = $(o.nav_id, div);
1319         var nav_li = $("li", nav);
1320         var nav_size = nav_li.size();
1321         var frame = div.find(o.frame_id);
1322         var content_width = $(frame).find('ul').width();
1323         //Buttons
1324         $(nav_li).click(function(e) {
1325           go($(nav_li).index($(this)));
1326         })
1327
1328         //Go to an item
1329         function go(to) {
1330             if(!running) {
1331                 curr = to;
1332                 running = true;
1333
1334                 frame.animate({ 'margin-left' : -(curr*content_width) }, o.speed, o.easing,
1335                     function() {
1336                         running = false;
1337                     }
1338                 );
1339
1340
1341                 nav_li.removeClass('active');
1342                 nav_li.eq(to).addClass('active');
1343
1344
1345             }
1346             return false;
1347         };
1348     });
1349 };
1350
1351 function css(el, prop) {
1352     return parseInt($.css(el[0], prop)) || 0;
1353 };
1354 function width(el) {
1355     return  el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1356 };
1357 function height(el) {
1358     return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1359 };
1360
1361 })(jQuery);
1362
1363
1364
1365
1366
1367/* ######################################################## */
1368/* ################  SEARCH SUGGESTIONS  ################## */
1369/* ######################################################## */
1370
1371
1372var gSelectedIndex = -1;
1373var gSelectedID = -1;
1374var gMatches = new Array();
1375var gLastText = "";
1376var ROW_COUNT = 20;
1377var gInitialized = false;
1378
1379function set_item_selected($li, selected)
1380{
1381    if (selected) {
1382        $li.attr('class','jd-autocomplete jd-selected');
1383    } else {
1384        $li.attr('class','jd-autocomplete');
1385    }
1386}
1387
1388function set_item_values(toroot, $li, match)
1389{
1390    var $link = $('a',$li);
1391    $link.html(match.__hilabel || match.label);
1392    $link.attr('href',toroot + match.link);
1393}
1394
1395function sync_selection_table(toroot)
1396{
1397    var $list = $("#search_filtered");
1398    var $li; //list item jquery object
1399    var i; //list item iterator
1400    gSelectedID = -1;
1401
1402    //initialize the table; draw it for the first time (but not visible).
1403    if (!gInitialized) {
1404        for (i=0; i<ROW_COUNT; i++) {
1405            var $li = $("<li class='jd-autocomplete'></li>");
1406            $list.append($li);
1407
1408            $li.mousedown(function() {
1409                window.location = this.firstChild.getAttribute("href");
1410            });
1411            $li.mouseover(function() {
1412                $('#search_filtered li').removeClass('jd-selected');
1413                $(this).addClass('jd-selected');
1414                gSelectedIndex = $('#search_filtered li').index(this);
1415            });
1416            $li.append('<a></a>');
1417        }
1418        gInitialized = true;
1419    }
1420
1421    //if we have results, make the table visible and initialize result info
1422    if (gMatches.length > 0) {
1423        $('#search_filtered_div').removeClass('no-display');
1424        var N = gMatches.length < ROW_COUNT ? gMatches.length : ROW_COUNT;
1425        for (i=0; i<N; i++) {
1426            $li = $('#search_filtered li:nth-child('+(i+1)+')');
1427            $li.attr('class','show-item');
1428            set_item_values(toroot, $li, gMatches[i]);
1429            set_item_selected($li, i == gSelectedIndex);
1430            if (i == gSelectedIndex) {
1431                gSelectedID = gMatches[i].id;
1432            }
1433        }
1434        //start hiding rows that are no longer matches
1435        for (; i<ROW_COUNT; i++) {
1436            $li = $('#search_filtered li:nth-child('+(i+1)+')');
1437            $li.attr('class','no-display');
1438        }
1439        //if there are more results we're not showing, so say so.
1440/*      if (gMatches.length > ROW_COUNT) {
1441            li = list.rows[ROW_COUNT];
1442            li.className = "show-item";
1443            c1 = li.cells[0];
1444            c1.innerHTML = "plus " + (gMatches.length-ROW_COUNT) + " more";
1445        } else {
1446            list.rows[ROW_COUNT].className = "hide-item";
1447        }*/
1448    //if we have no results, hide the table
1449    } else {
1450        $('#search_filtered_div').addClass('no-display');
1451    }
1452}
1453
1454function search_changed(e, kd, toroot)
1455{
1456    var search = document.getElementById("search_autocomplete");
1457    var text = search.value.replace(/(^ +)|( +$)/g, '');
1458
1459    // show/hide the close button
1460    if (text != '') {
1461        $(".search .close").removeClass("hide");
1462    } else {
1463        $(".search .close").addClass("hide");
1464    }
1465
1466    // 13 = enter
1467    if (e.keyCode == 13) {
1468        $('#search_filtered_div').addClass('no-display');
1469        if (!$('#search_filtered_div').hasClass('no-display') || (gSelectedIndex < 0)) {
1470            if ($("#searchResults").is(":hidden")) {
1471              // if results aren't showing, return true to allow search to execute
1472              return true;
1473            } else {
1474              // otherwise, results are already showing, so allow ajax to auto refresh the results
1475              // and ignore this Enter press to avoid the reload.
1476              return false;
1477            }
1478        } else if (kd && gSelectedIndex >= 0) {
1479            window.location = toroot + gMatches[gSelectedIndex].link;
1480            return false;
1481        }
1482    }
1483    // 38 -- arrow up
1484    else if (kd && (e.keyCode == 38)) {
1485        if (gSelectedIndex >= 0) {
1486            $('#search_filtered li').removeClass('jd-selected');
1487            gSelectedIndex--;
1488            $('#search_filtered li:nth-child('+(gSelectedIndex+1)+')').addClass('jd-selected');
1489        }
1490        return false;
1491    }
1492    // 40 -- arrow down
1493    else if (kd && (e.keyCode == 40)) {
1494        if (gSelectedIndex < gMatches.length-1
1495                        && gSelectedIndex < ROW_COUNT-1) {
1496            $('#search_filtered li').removeClass('jd-selected');
1497            gSelectedIndex++;
1498            $('#search_filtered li:nth-child('+(gSelectedIndex+1)+')').addClass('jd-selected');
1499        }
1500        return false;
1501    }
1502    else if (!kd && (e.keyCode != 40) && (e.keyCode != 38)) {
1503        gMatches = new Array();
1504        matchedCount = 0;
1505        gSelectedIndex = -1;
1506        for (var i=0; i<DATA.length; i++) {
1507            var s = DATA[i];
1508            if (text.length != 0 &&
1509                  s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
1510                gMatches[matchedCount] = s;
1511                matchedCount++;
1512            }
1513        }
1514        rank_autocomplete_results(text);
1515        for (var i=0; i<gMatches.length; i++) {
1516            var s = gMatches[i];
1517            if (gSelectedID == s.id) {
1518                gSelectedIndex = i;
1519            }
1520        }
1521        highlight_autocomplete_result_labels(text);
1522        sync_selection_table(toroot);
1523        return true; // allow the event to bubble up to the search api
1524    }
1525}
1526
1527function rank_autocomplete_results(query) {
1528    query = query || '';
1529    if (!gMatches || !gMatches.length)
1530      return;
1531
1532    // helper function that gets the last occurence index of the given regex
1533    // in the given string, or -1 if not found
1534    var _lastSearch = function(s, re) {
1535      if (s == '')
1536        return -1;
1537      var l = -1;
1538      var tmp;
1539      while ((tmp = s.search(re)) >= 0) {
1540        if (l < 0) l = 0;
1541        l += tmp;
1542        s = s.substr(tmp + 1);
1543      }
1544      return l;
1545    };
1546
1547    // helper function that counts the occurrences of a given character in
1548    // a given string
1549    var _countChar = function(s, c) {
1550      var n = 0;
1551      for (var i=0; i<s.length; i++)
1552        if (s.charAt(i) == c) ++n;
1553      return n;
1554    };
1555
1556    var queryLower = query.toLowerCase();
1557    var queryAlnum = (queryLower.match(/\w+/) || [''])[0];
1558    var partPrefixAlnumRE = new RegExp('\\b' + queryAlnum);
1559    var partExactAlnumRE = new RegExp('\\b' + queryAlnum + '\\b');
1560
1561    var _resultScoreFn = function(result) {
1562        // scores are calculated based on exact and prefix matches,
1563        // and then number of path separators (dots) from the last
1564        // match (i.e. favoring classes and deep package names)
1565        var score = 1.0;
1566        var labelLower = result.label.toLowerCase();
1567        var t;
1568        t = _lastSearch(labelLower, partExactAlnumRE);
1569        if (t >= 0) {
1570            // exact part match
1571            var partsAfter = _countChar(labelLower.substr(t + 1), '.');
1572            score *= 200 / (partsAfter + 1);
1573        } else {
1574            t = _lastSearch(labelLower, partPrefixAlnumRE);
1575            if (t >= 0) {
1576                // part prefix match
1577                var partsAfter = _countChar(labelLower.substr(t + 1), '.');
1578                score *= 20 / (partsAfter + 1);
1579            }
1580        }
1581
1582        return score;
1583    };
1584
1585    for (var i=0; i<gMatches.length; i++) {
1586        gMatches[i].__resultScore = _resultScoreFn(gMatches[i]);
1587    }
1588
1589    gMatches.sort(function(a,b){
1590        var n = b.__resultScore - a.__resultScore;
1591        if (n == 0) // lexicographical sort if scores are the same
1592            n = (a.label < b.label) ? -1 : 1;
1593        return n;
1594    });
1595}
1596
1597function highlight_autocomplete_result_labels(query) {
1598    query = query || '';
1599    if (!gMatches || !gMatches.length)
1600      return;
1601
1602    var queryLower = query.toLowerCase();
1603    var queryAlnumDot = (queryLower.match(/[\w\.]+/) || [''])[0];
1604    var queryRE = new RegExp(
1605        '(' + queryAlnumDot.replace(/\./g, '\\.') + ')', 'ig');
1606    for (var i=0; i<gMatches.length; i++) {
1607        gMatches[i].__hilabel = gMatches[i].label.replace(
1608            queryRE, '<b>$1</b>');
1609    }
1610}
1611
1612function search_focus_changed(obj, focused)
1613{
1614    if (!focused) {
1615        if(obj.value == ""){
1616          $(".search .close").addClass("hide");
1617        }
1618        document.getElementById("search_filtered_div").className = "no-display";
1619    }
1620}
1621
1622function submit_search() {
1623  var query = document.getElementById('search_autocomplete').value;
1624  location.hash = 'q=' + query;
1625  loadSearchResults();
1626  $("#searchResults").slideDown('slow');
1627  return false;
1628}
1629
1630
1631function hideResults() {
1632  $("#searchResults").slideUp();
1633  $(".search .close").addClass("hide");
1634  location.hash = '';
1635
1636  $("#search_autocomplete").val("").blur();
1637
1638  // reset the ajax search callback to nothing, so results don't appear unless ENTER
1639  searchControl.setSearchStartingCallback(this, function(control, searcher, query) {});
1640  return false;
1641}
1642
1643
1644
1645/* ########################################################## */
1646/* ################  CUSTOM SEARCH ENGINE  ################## */
1647/* ########################################################## */
1648
1649google.load('search', '1');
1650var searchControl;
1651
1652function loadSearchResults() {
1653  document.getElementById("search_autocomplete").style.color = "#000";
1654
1655  // create search control
1656  searchControl = new google.search.SearchControl();
1657
1658  // use our existing search form and use tabs when multiple searchers are used
1659  drawOptions = new google.search.DrawOptions();
1660  drawOptions.setDrawMode(google.search.SearchControl.DRAW_MODE_TABBED);
1661  drawOptions.setInput(document.getElementById("search_autocomplete"));
1662
1663  // configure search result options
1664  searchOptions = new google.search.SearcherOptions();
1665  searchOptions.setExpandMode(GSearchControl.EXPAND_MODE_OPEN);
1666
1667  // configure each of the searchers, for each tab
1668  devSiteSearcher = new google.search.WebSearch();
1669  devSiteSearcher.setUserDefinedLabel("All");
1670  devSiteSearcher.setSiteRestriction("001482626316274216503:zu90b7s047u");
1671
1672  designSearcher = new google.search.WebSearch();
1673  designSearcher.setUserDefinedLabel("Design");
1674  designSearcher.setSiteRestriction("http://developer.android.com/design/");
1675
1676  trainingSearcher = new google.search.WebSearch();
1677  trainingSearcher.setUserDefinedLabel("Training");
1678  trainingSearcher.setSiteRestriction("http://developer.android.com/training/");
1679
1680  guidesSearcher = new google.search.WebSearch();
1681  guidesSearcher.setUserDefinedLabel("Guides");
1682  guidesSearcher.setSiteRestriction("http://developer.android.com/guide/");
1683
1684  referenceSearcher = new google.search.WebSearch();
1685  referenceSearcher.setUserDefinedLabel("Reference");
1686  referenceSearcher.setSiteRestriction("http://developer.android.com/reference/");
1687
1688  blogSearcher = new google.search.WebSearch();
1689  blogSearcher.setUserDefinedLabel("Blog");
1690  blogSearcher.setSiteRestriction("http://android-developers.blogspot.com");
1691
1692  // add each searcher to the search control
1693  searchControl.addSearcher(devSiteSearcher, searchOptions);
1694  searchControl.addSearcher(designSearcher, searchOptions);
1695  searchControl.addSearcher(trainingSearcher, searchOptions);
1696  searchControl.addSearcher(guidesSearcher, searchOptions);
1697  searchControl.addSearcher(referenceSearcher, searchOptions);
1698  searchControl.addSearcher(blogSearcher, searchOptions);
1699
1700  // configure result options
1701  searchControl.setResultSetSize(google.search.Search.LARGE_RESULTSET);
1702  searchControl.setLinkTarget(google.search.Search.LINK_TARGET_SELF);
1703  searchControl.setTimeoutInterval(google.search.SearchControl.TIMEOUT_SHORT);
1704  searchControl.setNoResultsString(google.search.SearchControl.NO_RESULTS_DEFAULT_STRING);
1705
1706  // upon ajax search, refresh the url and search title
1707  searchControl.setSearchStartingCallback(this, function(control, searcher, query) {
1708    updateResultTitle(query);
1709    var query = document.getElementById('search_autocomplete').value;
1710    location.hash = 'q=' + query;
1711  });
1712
1713  // draw the search results box
1714  searchControl.draw(document.getElementById("leftSearchControl"), drawOptions);
1715
1716  // get query and execute the search
1717  searchControl.execute(decodeURI(getQuery(location.hash)));
1718
1719  document.getElementById("search_autocomplete").focus();
1720  addTabListeners();
1721}
1722// End of loadSearchResults
1723
1724
1725google.setOnLoadCallback(function(){
1726  if (location.hash.indexOf("q=") == -1) {
1727    // if there's no query in the url, don't search and make sure results are hidden
1728    $('#searchResults').hide();
1729    return;
1730  } else {
1731    // first time loading search results for this page
1732    $('#searchResults').slideDown('slow');
1733    $(".search .close").removeClass("hide");
1734    loadSearchResults();
1735  }
1736}, true);
1737
1738// when an event on the browser history occurs (back, forward, load) requery hash and do search
1739$(window).hashchange( function(){
1740  // Exit if the hash isn't a search query or there's an error in the query
1741  if ((location.hash.indexOf("q=") == -1) || (query == "undefined")) {
1742    // If the results pane is open, close it.
1743    if (!$("#searchResults").is(":hidden")) {
1744      hideResults();
1745    }
1746    return;
1747  }
1748
1749  // Otherwise, we have a search to do
1750  var query = decodeURI(getQuery(location.hash));
1751  searchControl.execute(query);
1752  $('#searchResults').slideDown('slow');
1753  $("#search_autocomplete").focus();
1754  $(".search .close").removeClass("hide");
1755
1756  updateResultTitle(query);
1757});
1758
1759function updateResultTitle(query) {
1760  $("#searchTitle").html("Results for <em>" + escapeHTML(query) + "</em>");
1761}
1762
1763// forcefully regain key-up event control (previously jacked by search api)
1764$("#search_autocomplete").keyup(function(event) {
1765  return search_changed(event, false, toRoot);
1766});
1767
1768// add event listeners to each tab so we can track the browser history
1769function addTabListeners() {
1770  var tabHeaders = $(".gsc-tabHeader");
1771  for (var i = 0; i < tabHeaders.length; i++) {
1772    $(tabHeaders[i]).attr("id",i).click(function() {
1773    /*
1774      // make a copy of the page numbers for the search left pane
1775      setTimeout(function() {
1776        // remove any residual page numbers
1777        $('#searchResults .gsc-tabsArea .gsc-cursor-box.gs-bidi-start-align').remove();
1778        // move the page numbers to the left position; make a clone,
1779        // because the element is drawn to the DOM only once
1780        // and because we're going to remove it (previous line),
1781        // we need it to be available to move again as the user navigates
1782        $('#searchResults .gsc-webResult .gsc-cursor-box.gs-bidi-start-align:visible')
1783                        .clone().appendTo('#searchResults .gsc-tabsArea');
1784        }, 200);
1785      */
1786    });
1787  }
1788  setTimeout(function(){$(tabHeaders[0]).click()},200);
1789}
1790
1791
1792function getQuery(hash) {
1793  var queryParts = hash.split('=');
1794  return queryParts[1];
1795}
1796
1797/* returns the given string with all HTML brackets converted to entities
1798    TODO: move this to the site's JS library */
1799function escapeHTML(string) {
1800  return string.replace(/</g,"&lt;")
1801                .replace(/>/g,"&gt;");
1802}
1803
1804
1805
1806
1807
1808
1809
1810/* ######################################################## */
1811/* #################  JAVADOC REFERENCE ################### */
1812/* ######################################################## */
1813
1814/* Initialize some droiddoc stuff, but only if we're in the reference */
1815if (location.pathname.indexOf("/reference") == 0) {
1816  $(document).ready(function() {
1817    // init available apis based on user pref
1818    changeApiLevel();
1819    initSidenavHeightResize()
1820  });
1821}
1822
1823var API_LEVEL_COOKIE = "api_level";
1824var minLevel = 1;
1825var maxLevel = 1;
1826
1827/******* SIDENAV DIMENSIONS ************/
1828
1829  function initSidenavHeightResize() {
1830    // Change the drag bar size to nicely fit the scrollbar positions
1831    var $dragBar = $(".ui-resizable-s");
1832    $dragBar.css({'width': $dragBar.parent().width() - 5 + "px"});
1833
1834    $( "#resize-packages-nav" ).resizable({
1835      containment: "#nav-panels",
1836      handles: "s",
1837      alsoResize: "#packages-nav",
1838      resize: function(event, ui) { resizeNav(); }, /* resize the nav while dragging */
1839      stop: function(event, ui) { saveNavPanels(); } /* once stopped, save the sizes to cookie  */
1840      });
1841
1842  }
1843
1844function updateSidenavFixedWidth() {
1845  if (!navBarIsFixed) return;
1846  $('#devdoc-nav').css({
1847    'width' : $('#side-nav').css('width'),
1848    'margin' : $('#side-nav').css('margin')
1849  });
1850  $('#devdoc-nav a.totop').css({'display':'block','width':$("#nav").innerWidth()+'px'});
1851
1852  initSidenavHeightResize();
1853}
1854
1855function updateSidenavFullscreenWidth() {
1856  if (!navBarIsFixed) return;
1857  $('#devdoc-nav').css({
1858    'width' : $('#side-nav').css('width'),
1859    'margin' : $('#side-nav').css('margin')
1860  });
1861  $('#devdoc-nav .totop').css({'left': 'inherit'});
1862
1863  initSidenavHeightResize();
1864}
1865
1866function buildApiLevelSelector() {
1867  maxLevel = SINCE_DATA.length;
1868  var userApiLevel = parseInt(readCookie(API_LEVEL_COOKIE));
1869  userApiLevel = userApiLevel == 0 ? maxLevel : userApiLevel; // If there's no cookie (zero), use the max by default
1870
1871  minLevel = parseInt($("#doc-api-level").attr("class"));
1872  // Handle provisional api levels; the provisional level will always be the highest possible level
1873  // Provisional api levels will also have a length; other stuff that's just missing a level won't,
1874  // so leave those kinds of entities at the default level of 1 (for example, the R.styleable class)
1875  if (isNaN(minLevel) && minLevel.length) {
1876    minLevel = maxLevel;
1877  }
1878  var select = $("#apiLevelSelector").html("").change(changeApiLevel);
1879  for (var i = maxLevel-1; i >= 0; i--) {
1880    var option = $("<option />").attr("value",""+SINCE_DATA[i]).append(""+SINCE_DATA[i]);
1881  //  if (SINCE_DATA[i] < minLevel) option.addClass("absent"); // always false for strings (codenames)
1882    select.append(option);
1883  }
1884
1885  // get the DOM element and use setAttribute cuz IE6 fails when using jquery .attr('selected',true)
1886  var selectedLevelItem = $("#apiLevelSelector option[value='"+userApiLevel+"']").get(0);
1887  selectedLevelItem.setAttribute('selected',true);
1888}
1889
1890function changeApiLevel() {
1891  maxLevel = SINCE_DATA.length;
1892  var selectedLevel = maxLevel;
1893
1894  selectedLevel = parseInt($("#apiLevelSelector option:selected").val());
1895  toggleVisisbleApis(selectedLevel, "body");
1896
1897  var date = new Date();
1898  date.setTime(date.getTime()+(10*365*24*60*60*1000)); // keep this for 10 years
1899  var expiration = date.toGMTString();
1900  writeCookie(API_LEVEL_COOKIE, selectedLevel, null, expiration);
1901
1902  if (selectedLevel < minLevel) {
1903    var thing = ($("#jd-header").html().indexOf("package") != -1) ? "package" : "class";
1904    $("#naMessage").show().html("<div><p><strong>This " + thing + " is not available with API level " + selectedLevel + ".</strong></p>"
1905                              + "<p>To use this " + thing + ", you must develop your app using a build target "
1906                              + "that supports API level " + $("#doc-api-level").attr("class") + " or higher. To read these "
1907                              + "APIs, change the value of the API level filter above.</p>"
1908                              + "<p><a href='" +toRoot+ "guide/appendix/api-levels.html'>What is the API level?</a></p></div>");
1909  } else {
1910    $("#naMessage").hide();
1911  }
1912}
1913
1914function toggleVisisbleApis(selectedLevel, context) {
1915  var apis = $(".api",context);
1916  apis.each(function(i) {
1917    var obj = $(this);
1918    var className = obj.attr("class");
1919    var apiLevelIndex = className.lastIndexOf("-")+1;
1920    var apiLevelEndIndex = className.indexOf(" ", apiLevelIndex);
1921    apiLevelEndIndex = apiLevelEndIndex != -1 ? apiLevelEndIndex : className.length;
1922    var apiLevel = className.substring(apiLevelIndex, apiLevelEndIndex);
1923    if (apiLevel.length == 0) { // for odd cases when the since data is actually missing, just bail
1924      return;
1925    }
1926    apiLevel = parseInt(apiLevel);
1927
1928    // Handle provisional api levels; if this item's level is the provisional one, set it to the max
1929    var selectedLevelNum = parseInt(selectedLevel)
1930    var apiLevelNum = parseInt(apiLevel);
1931    if (isNaN(apiLevelNum)) {
1932        apiLevelNum = maxLevel;
1933    }
1934
1935    // Grey things out that aren't available and give a tooltip title
1936    if (apiLevelNum > selectedLevelNum) {
1937      obj.addClass("absent").attr("title","Requires API Level \""
1938            + apiLevel + "\" or higher");
1939    }
1940    else obj.removeClass("absent").removeAttr("title");
1941  });
1942}
1943
1944
1945
1946
1947/* #################  SIDENAV TREE VIEW ################### */
1948
1949function new_node(me, mom, text, link, children_data, api_level)
1950{
1951  var node = new Object();
1952  node.children = Array();
1953  node.children_data = children_data;
1954  node.depth = mom.depth + 1;
1955
1956  node.li = document.createElement("li");
1957  mom.get_children_ul().appendChild(node.li);
1958
1959  node.label_div = document.createElement("div");
1960  node.label_div.className = "label";
1961  if (api_level != null) {
1962    $(node.label_div).addClass("api");
1963    $(node.label_div).addClass("api-level-"+api_level);
1964  }
1965  node.li.appendChild(node.label_div);
1966
1967  if (children_data != null) {
1968    node.expand_toggle = document.createElement("a");
1969    node.expand_toggle.href = "javascript:void(0)";
1970    node.expand_toggle.onclick = function() {
1971          if (node.expanded) {
1972            $(node.get_children_ul()).slideUp("fast");
1973            node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
1974            node.expanded = false;
1975          } else {
1976            expand_node(me, node);
1977          }
1978       };
1979    node.label_div.appendChild(node.expand_toggle);
1980
1981    node.plus_img = document.createElement("img");
1982    node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
1983    node.plus_img.className = "plus";
1984    node.plus_img.width = "8";
1985    node.plus_img.border = "0";
1986    node.expand_toggle.appendChild(node.plus_img);
1987
1988    node.expanded = false;
1989  }
1990
1991  var a = document.createElement("a");
1992  node.label_div.appendChild(a);
1993  node.label = document.createTextNode(text);
1994  a.appendChild(node.label);
1995  if (link) {
1996    a.href = me.toroot + link;
1997  } else {
1998    if (children_data != null) {
1999      a.className = "nolink";
2000      a.href = "javascript:void(0)";
2001      a.onclick = node.expand_toggle.onclick;
2002      // This next line shouldn't be necessary.  I'll buy a beer for the first
2003      // person who figures out how to remove this line and have the link
2004      // toggle shut on the first try. --joeo@android.com
2005      node.expanded = false;
2006    }
2007  }
2008
2009
2010  node.children_ul = null;
2011  node.get_children_ul = function() {
2012      if (!node.children_ul) {
2013        node.children_ul = document.createElement("ul");
2014        node.children_ul.className = "children_ul";
2015        node.children_ul.style.display = "none";
2016        node.li.appendChild(node.children_ul);
2017      }
2018      return node.children_ul;
2019    };
2020
2021  return node;
2022}
2023
2024function expand_node(me, node)
2025{
2026  if (node.children_data && !node.expanded) {
2027    if (node.children_visited) {
2028      $(node.get_children_ul()).slideDown("fast");
2029    } else {
2030      get_node(me, node);
2031      if ($(node.label_div).hasClass("absent")) {
2032        $(node.get_children_ul()).addClass("absent");
2033      }
2034      $(node.get_children_ul()).slideDown("fast");
2035    }
2036    node.plus_img.src = me.toroot + "assets/images/triangle-opened-small.png";
2037    node.expanded = true;
2038
2039    // perform api level toggling because new nodes are new to the DOM
2040    var selectedLevel = $("#apiLevelSelector option:selected").val();
2041    toggleVisisbleApis(selectedLevel, "#side-nav");
2042  }
2043}
2044
2045function get_node(me, mom)
2046{
2047  mom.children_visited = true;
2048  for (var i in mom.children_data) {
2049    var node_data = mom.children_data[i];
2050    mom.children[i] = new_node(me, mom, node_data[0], node_data[1],
2051        node_data[2], node_data[3]);
2052  }
2053}
2054
2055function this_page_relative(toroot)
2056{
2057  var full = document.location.pathname;
2058  var file = "";
2059  if (toroot.substr(0, 1) == "/") {
2060    if (full.substr(0, toroot.length) == toroot) {
2061      return full.substr(toroot.length);
2062    } else {
2063      // the file isn't under toroot.  Fail.
2064      return null;
2065    }
2066  } else {
2067    if (toroot != "./") {
2068      toroot = "./" + toroot;
2069    }
2070    do {
2071      if (toroot.substr(toroot.length-3, 3) == "../" || toroot == "./") {
2072        var pos = full.lastIndexOf("/");
2073        file = full.substr(pos) + file;
2074        full = full.substr(0, pos);
2075        toroot = toroot.substr(0, toroot.length-3);
2076      }
2077    } while (toroot != "" && toroot != "/");
2078    return file.substr(1);
2079  }
2080}
2081
2082function find_page(url, data)
2083{
2084  var nodes = data;
2085  var result = null;
2086  for (var i in nodes) {
2087    var d = nodes[i];
2088    if (d[1] == url) {
2089      return new Array(i);
2090    }
2091    else if (d[2] != null) {
2092      result = find_page(url, d[2]);
2093      if (result != null) {
2094        return (new Array(i).concat(result));
2095      }
2096    }
2097  }
2098  return null;
2099}
2100
2101function load_navtree_data(toroot) {
2102  var navtreeData = document.createElement("script");
2103  navtreeData.setAttribute("type","text/javascript");
2104  navtreeData.setAttribute("src", toroot+"navtree_data.js");
2105  $("head").append($(navtreeData));
2106}
2107
2108function init_default_navtree(toroot) {
2109  init_navtree("tree-list", toroot, NAVTREE_DATA);
2110
2111  // perform api level toggling because because the whole tree is new to the DOM
2112  var selectedLevel = $("#apiLevelSelector option:selected").val();
2113  toggleVisisbleApis(selectedLevel, "#side-nav");
2114}
2115
2116function init_navtree(navtree_id, toroot, root_nodes)
2117{
2118  var me = new Object();
2119  me.toroot = toroot;
2120  me.node = new Object();
2121
2122  me.node.li = document.getElementById(navtree_id);
2123  me.node.children_data = root_nodes;
2124  me.node.children = new Array();
2125  me.node.children_ul = document.createElement("ul");
2126  me.node.get_children_ul = function() { return me.node.children_ul; };
2127  //me.node.children_ul.className = "children_ul";
2128  me.node.li.appendChild(me.node.children_ul);
2129  me.node.depth = 0;
2130
2131  get_node(me, me.node);
2132
2133  me.this_page = this_page_relative(toroot);
2134  me.breadcrumbs = find_page(me.this_page, root_nodes);
2135  if (me.breadcrumbs != null && me.breadcrumbs.length != 0) {
2136    var mom = me.node;
2137    for (var i in me.breadcrumbs) {
2138      var j = me.breadcrumbs[i];
2139      mom = mom.children[j];
2140      expand_node(me, mom);
2141    }
2142    mom.label_div.className = mom.label_div.className + " selected";
2143    addLoadEvent(function() {
2144      scrollIntoView("nav-tree");
2145      });
2146  }
2147}
2148
2149/* TOGGLE INHERITED MEMBERS */
2150
2151/* Toggle an inherited class (arrow toggle)
2152 * @param linkObj  The link that was clicked.
2153 * @param expand  'true' to ensure it's expanded. 'false' to ensure it's closed.
2154 *                'null' to simply toggle.
2155 */
2156function toggleInherited(linkObj, expand) {
2157    var base = linkObj.getAttribute("id");
2158    var list = document.getElementById(base + "-list");
2159    var summary = document.getElementById(base + "-summary");
2160    var trigger = document.getElementById(base + "-trigger");
2161    var a = $(linkObj);
2162    if ( (expand == null && a.hasClass("closed")) || expand ) {
2163        list.style.display = "none";
2164        summary.style.display = "block";
2165        trigger.src = toRoot + "assets/images/triangle-opened.png";
2166        a.removeClass("closed");
2167        a.addClass("opened");
2168    } else if ( (expand == null && a.hasClass("opened")) || (expand == false) ) {
2169        list.style.display = "block";
2170        summary.style.display = "none";
2171        trigger.src = toRoot + "assets/images/triangle-closed.png";
2172        a.removeClass("opened");
2173        a.addClass("closed");
2174    }
2175    return false;
2176}
2177
2178/* Toggle all inherited classes in a single table (e.g. all inherited methods)
2179 * @param linkObj  The link that was clicked.
2180 * @param expand  'true' to ensure it's expanded. 'false' to ensure it's closed.
2181 *                'null' to simply toggle.
2182 */
2183function toggleAllInherited(linkObj, expand) {
2184  var a = $(linkObj);
2185  var table = $(a.parent().parent().parent()); // ugly way to get table/tbody
2186  var expandos = $(".jd-expando-trigger", table);
2187  if ( (expand == null && a.text() == "[Expand]") || expand ) {
2188    expandos.each(function(i) {
2189      toggleInherited(this, true);
2190    });
2191    a.text("[Collapse]");
2192  } else if ( (expand == null && a.text() == "[Collapse]") || (expand == false) ) {
2193    expandos.each(function(i) {
2194      toggleInherited(this, false);
2195    });
2196    a.text("[Expand]");
2197  }
2198  return false;
2199}
2200
2201/* Toggle all inherited members in the class (link in the class title)
2202 */
2203function toggleAllClassInherited() {
2204  var a = $("#toggleAllClassInherited"); // get toggle link from class title
2205  var toggles = $(".toggle-all", $("#body-content"));
2206  if (a.text() == "[Expand All]") {
2207    toggles.each(function(i) {
2208      toggleAllInherited(this, true);
2209    });
2210    a.text("[Collapse All]");
2211  } else {
2212    toggles.each(function(i) {
2213      toggleAllInherited(this, false);
2214    });
2215    a.text("[Expand All]");
2216  }
2217  return false;
2218}
2219
2220/* Expand all inherited members in the class. Used when initiating page search */
2221function ensureAllInheritedExpanded() {
2222  var toggles = $(".toggle-all", $("#body-content"));
2223  toggles.each(function(i) {
2224    toggleAllInherited(this, true);
2225  });
2226  $("#toggleAllClassInherited").text("[Collapse All]");
2227}
2228
2229
2230/* HANDLE KEY EVENTS
2231 * - Listen for Ctrl+F (Cmd on Mac) and expand all inherited members (to aid page search)
2232 */
2233var agent = navigator['userAgent'].toLowerCase();
2234var mac = agent.indexOf("macintosh") != -1;
2235
2236$(document).keydown( function(e) {
2237var control = mac ? e.metaKey && !e.ctrlKey : e.ctrlKey; // get ctrl key
2238  if (control && e.which == 70) {  // 70 is "F"
2239    ensureAllInheritedExpanded();
2240  }
2241});
2242
2243
2244
2245