docs.js revision db3678bcaadf74b1a940a3dafb9fda4e238250be
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').not('h2.norule').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,.tooltip-link').each(function() {
388    var $target = $(this);
389    var $tooltip = $('<div>')
390        .addClass('tooltip-box')
391        .append($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 changeNavLang(lang) {
983  var $links = $("#devdoc-nav,#header,#nav-x,.training-nav-top,.content-footer").find("a["+lang+"-lang]");
984  $links.each(function(i){ // for each link with a translation
985    var $link = $(this);
986    if (lang != "en") { // No need to worry about English, because a language change invokes new request
987      // put the desired language from the attribute as the text
988      $link.text($link.attr(lang+"-lang"))
989    }
990  });
991}
992
993function changeDocLang(lang) {
994  changeNavLang(lang);
995}
996
997function changeLangPref(lang, refresh) {
998  var date = new Date();
999  expires = date.toGMTString(date.setTime(date.getTime()+(10*365*24*60*60*1000)));
1000  // keep this for 50 years
1001  //alert("expires: " + expires)
1002  writeCookie("pref_lang", lang, null, expires);
1003  changeDocLang(lang);
1004  if (refresh) {
1005    l = getBaseUri(location.pathname);
1006    window.location = l;
1007  }
1008}
1009
1010function loadLangPref() {
1011  var lang = readCookie("pref_lang");
1012  if (lang != 0) {
1013    $("#language").find("option[value='"+lang+"']").attr("selected",true);
1014  }
1015}
1016
1017function getLangPref() {
1018  var lang = $("#language").find(":selected").attr("value");
1019  if (!lang) {
1020    lang = readCookie("pref_lang");
1021  }
1022  return (lang != 0) ? lang : 'en';
1023}
1024
1025/* ##########     END LOCALIZATION     ############ */
1026
1027
1028
1029
1030
1031
1032/* Used to hide and reveal supplemental content, such as long code samples.
1033   See the companion CSS in android-developer-docs.css */
1034function toggleContent(obj) {
1035  var div = $(obj.parentNode.parentNode);
1036  var toggleMe = $(".toggle-content-toggleme",div);
1037  if (div.hasClass("closed")) { // if it's closed, open it
1038    toggleMe.slideDown();
1039    $(".toggle-content-text", obj).toggle();
1040    div.removeClass("closed").addClass("open");
1041    $(".toggle-content-img", div).attr("title", "hide").attr("src", toRoot
1042                  + "assets/images/triangle-opened.png");
1043  } else { // if it's open, close it
1044    toggleMe.slideUp('fast', function() {  // Wait until the animation is done before closing arrow
1045      $(".toggle-content-text", obj).toggle();
1046      div.removeClass("open").addClass("closed");
1047      $(".toggle-content-img", div).attr("title", "show").attr("src", toRoot
1048                  + "assets/images/triangle-closed.png");
1049    });
1050  }
1051  return false;
1052}
1053
1054
1055/* New version of expandable content */
1056function toggleExpandable(link,id) {
1057  if($(id).is(':visible')) {
1058    $(id).slideUp();
1059    $(link).removeClass('expanded');
1060  } else {
1061    $(id).slideDown();
1062    $(link).addClass('expanded');
1063  }
1064}
1065
1066function hideExpandable(ids) {
1067  $(ids).slideUp();
1068}
1069
1070
1071
1072
1073
1074/*
1075 *  Slideshow 1.0
1076 *  Used on /index.html and /develop/index.html for carousel
1077 *
1078 *  Sample usage:
1079 *  HTML -
1080 *  <div class="slideshow-container">
1081 *   <a href="" class="slideshow-prev">Prev</a>
1082 *   <a href="" class="slideshow-next">Next</a>
1083 *   <ul>
1084 *       <li class="item"><img src="images/marquee1.jpg"></li>
1085 *       <li class="item"><img src="images/marquee2.jpg"></li>
1086 *       <li class="item"><img src="images/marquee3.jpg"></li>
1087 *       <li class="item"><img src="images/marquee4.jpg"></li>
1088 *   </ul>
1089 *  </div>
1090 *
1091 *   <script type="text/javascript">
1092 *   $('.slideshow-container').dacSlideshow({
1093 *       auto: true,
1094 *       btnPrev: '.slideshow-prev',
1095 *       btnNext: '.slideshow-next'
1096 *   });
1097 *   </script>
1098 *
1099 *  Options:
1100 *  btnPrev:    optional identifier for previous button
1101 *  btnNext:    optional identifier for next button
1102 *  auto:       whether or not to auto-proceed
1103 *  speed:      animation speed
1104 *  autoTime:   time between auto-rotation
1105 *  easing:     easing function for transition
1106 *  start:      item to select by default
1107 *  scroll:     direction to scroll in
1108 *  pagination: whether or not to include dotted pagination
1109 *
1110 */
1111
1112 (function($) {
1113 $.fn.dacSlideshow = function(o) {
1114
1115     //Options - see above
1116     o = $.extend({
1117         btnPrev:   null,
1118         btnNext:   null,
1119         auto:      true,
1120         speed:     500,
1121         autoTime:  12000,
1122         easing:    null,
1123         start:     0,
1124         scroll:    1,
1125         pagination: true
1126
1127     }, o || {});
1128
1129     //Set up a carousel for each
1130     return this.each(function() {
1131
1132         var running = false;
1133         var animCss = o.vertical ? "top" : "left";
1134         var sizeCss = o.vertical ? "height" : "width";
1135         var div = $(this);
1136         var ul = $("ul", div);
1137         var tLi = $("li", ul);
1138         var tl = tLi.size();
1139         var timer = null;
1140
1141         var li = $("li", ul);
1142         var itemLength = li.size();
1143         var curr = o.start;
1144
1145         li.css({float: o.vertical ? "none" : "left"});
1146         ul.css({margin: "0", padding: "0", position: "relative", "list-style-type": "none", "z-index": "1"});
1147         div.css({position: "relative", "z-index": "2", left: "0px"});
1148
1149         var liSize = o.vertical ? height(li) : width(li);
1150         var ulSize = liSize * itemLength;
1151         var divSize = liSize;
1152
1153         li.css({width: li.width(), height: li.height()});
1154         ul.css(sizeCss, ulSize+"px").css(animCss, -(curr*liSize));
1155
1156         div.css(sizeCss, divSize+"px");
1157
1158         //Pagination
1159         if (o.pagination) {
1160             var pagination = $("<div class='pagination'></div>");
1161             var pag_ul = $("<ul></ul>");
1162             if (tl > 1) {
1163               for (var i=0;i<tl;i++) {
1164                    var li = $("<li>"+i+"</li>");
1165                    pag_ul.append(li);
1166                    if (i==o.start) li.addClass('active');
1167                        li.click(function() {
1168                        go(parseInt($(this).text()));
1169                    })
1170                }
1171                pagination.append(pag_ul);
1172                div.append(pagination);
1173             }
1174         }
1175
1176         //Previous button
1177         if(o.btnPrev)
1178             $(o.btnPrev).click(function(e) {
1179                 e.preventDefault();
1180                 return go(curr-o.scroll);
1181             });
1182
1183         //Next button
1184         if(o.btnNext)
1185             $(o.btnNext).click(function(e) {
1186                 e.preventDefault();
1187                 return go(curr+o.scroll);
1188             });
1189
1190         //Auto rotation
1191         if(o.auto) startRotateTimer();
1192
1193         function startRotateTimer() {
1194             clearInterval(timer);
1195             timer = setInterval(function() {
1196                  if (curr == tl-1) {
1197                    go(0);
1198                  } else {
1199                    go(curr+o.scroll);
1200                  }
1201              }, o.autoTime);
1202         }
1203
1204         //Go to an item
1205         function go(to) {
1206             if(!running) {
1207
1208                 if(to<0) {
1209                    to = itemLength-1;
1210                 } else if (to>itemLength-1) {
1211                    to = 0;
1212                 }
1213                 curr = to;
1214
1215                 running = true;
1216
1217                 ul.animate(
1218                     animCss == "left" ? { left: -(curr*liSize) } : { top: -(curr*liSize) } , o.speed, o.easing,
1219                     function() {
1220                         running = false;
1221                     }
1222                 );
1223
1224                 $(o.btnPrev + "," + o.btnNext).removeClass("disabled");
1225                 $( (curr-o.scroll<0 && o.btnPrev)
1226                     ||
1227                    (curr+o.scroll > itemLength && o.btnNext)
1228                     ||
1229                    []
1230                  ).addClass("disabled");
1231
1232
1233                 var nav_items = $('li', pagination);
1234                 nav_items.removeClass('active');
1235                 nav_items.eq(to).addClass('active');
1236
1237
1238             }
1239             if(o.auto) startRotateTimer();
1240             return false;
1241         };
1242     });
1243 };
1244
1245 function css(el, prop) {
1246     return parseInt($.css(el[0], prop)) || 0;
1247 };
1248 function width(el) {
1249     return  el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1250 };
1251 function height(el) {
1252     return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1253 };
1254
1255 })(jQuery);
1256
1257
1258/*
1259 *  dacSlideshow 1.0
1260 *  Used on develop/index.html for side-sliding tabs
1261 *
1262 *  Sample usage:
1263 *  HTML -
1264 *  <div class="slideshow-container">
1265 *   <a href="" class="slideshow-prev">Prev</a>
1266 *   <a href="" class="slideshow-next">Next</a>
1267 *   <ul>
1268 *       <li class="item"><img src="images/marquee1.jpg"></li>
1269 *       <li class="item"><img src="images/marquee2.jpg"></li>
1270 *       <li class="item"><img src="images/marquee3.jpg"></li>
1271 *       <li class="item"><img src="images/marquee4.jpg"></li>
1272 *   </ul>
1273 *  </div>
1274 *
1275 *   <script type="text/javascript">
1276 *   $('.slideshow-container').dacSlideshow({
1277 *       auto: true,
1278 *       btnPrev: '.slideshow-prev',
1279 *       btnNext: '.slideshow-next'
1280 *   });
1281 *   </script>
1282 *
1283 *  Options:
1284 *  btnPrev:    optional identifier for previous button
1285 *  btnNext:    optional identifier for next button
1286 *  auto:       whether or not to auto-proceed
1287 *  speed:      animation speed
1288 *  autoTime:   time between auto-rotation
1289 *  easing:     easing function for transition
1290 *  start:      item to select by default
1291 *  scroll:     direction to scroll in
1292 *  pagination: whether or not to include dotted pagination
1293 *
1294 */
1295 (function($) {
1296 $.fn.dacTabbedList = function(o) {
1297
1298     //Options - see above
1299     o = $.extend({
1300         speed : 250,
1301         easing: null,
1302         nav_id: null,
1303         frame_id: null
1304     }, o || {});
1305
1306     //Set up a carousel for each
1307     return this.each(function() {
1308
1309         var curr = 0;
1310         var running = false;
1311         var animCss = "margin-left";
1312         var sizeCss = "width";
1313         var div = $(this);
1314
1315         var nav = $(o.nav_id, div);
1316         var nav_li = $("li", nav);
1317         var nav_size = nav_li.size();
1318         var frame = div.find(o.frame_id);
1319         var content_width = $(frame).find('ul').width();
1320         //Buttons
1321         $(nav_li).click(function(e) {
1322           go($(nav_li).index($(this)));
1323         })
1324
1325         //Go to an item
1326         function go(to) {
1327             if(!running) {
1328                 curr = to;
1329                 running = true;
1330
1331                 frame.animate({ 'margin-left' : -(curr*content_width) }, o.speed, o.easing,
1332                     function() {
1333                         running = false;
1334                     }
1335                 );
1336
1337
1338                 nav_li.removeClass('active');
1339                 nav_li.eq(to).addClass('active');
1340
1341
1342             }
1343             return false;
1344         };
1345     });
1346 };
1347
1348 function css(el, prop) {
1349     return parseInt($.css(el[0], prop)) || 0;
1350 };
1351 function width(el) {
1352     return  el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1353 };
1354 function height(el) {
1355     return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1356 };
1357
1358 })(jQuery);
1359
1360
1361
1362
1363
1364/* ######################################################## */
1365/* ################  SEARCH SUGGESTIONS  ################## */
1366/* ######################################################## */
1367
1368
1369var gSelectedIndex = -1;
1370var gSelectedID = -1;
1371var gMatches = new Array();
1372var gLastText = "";
1373var ROW_COUNT = 20;
1374var gInitialized = false;
1375
1376function set_item_selected($li, selected)
1377{
1378    if (selected) {
1379        $li.attr('class','jd-autocomplete jd-selected');
1380    } else {
1381        $li.attr('class','jd-autocomplete');
1382    }
1383}
1384
1385function set_item_values(toroot, $li, match)
1386{
1387    var $link = $('a',$li);
1388    $link.html(match.__hilabel || match.label);
1389    $link.attr('href',toroot + match.link);
1390}
1391
1392function sync_selection_table(toroot)
1393{
1394    var $list = $("#search_filtered");
1395    var $li; //list item jquery object
1396    var i; //list item iterator
1397    gSelectedID = -1;
1398
1399    //initialize the table; draw it for the first time (but not visible).
1400    if (!gInitialized) {
1401        for (i=0; i<ROW_COUNT; i++) {
1402            var $li = $("<li class='jd-autocomplete'></li>");
1403            $list.append($li);
1404
1405            $li.mousedown(function() {
1406                window.location = this.firstChild.getAttribute("href");
1407            });
1408            $li.mouseover(function() {
1409                $('#search_filtered li').removeClass('jd-selected');
1410                $(this).addClass('jd-selected');
1411                gSelectedIndex = $('#search_filtered li').index(this);
1412            });
1413            $li.append('<a></a>');
1414        }
1415        gInitialized = true;
1416    }
1417
1418    //if we have results, make the table visible and initialize result info
1419    if (gMatches.length > 0) {
1420        $('#search_filtered_div').removeClass('no-display');
1421        var N = gMatches.length < ROW_COUNT ? gMatches.length : ROW_COUNT;
1422        for (i=0; i<N; i++) {
1423            $li = $('#search_filtered li:nth-child('+(i+1)+')');
1424            $li.attr('class','show-item');
1425            set_item_values(toroot, $li, gMatches[i]);
1426            set_item_selected($li, i == gSelectedIndex);
1427            if (i == gSelectedIndex) {
1428                gSelectedID = gMatches[i].id;
1429            }
1430        }
1431        //start hiding rows that are no longer matches
1432        for (; i<ROW_COUNT; i++) {
1433            $li = $('#search_filtered li:nth-child('+(i+1)+')');
1434            $li.attr('class','no-display');
1435        }
1436        //if there are more results we're not showing, so say so.
1437/*      if (gMatches.length > ROW_COUNT) {
1438            li = list.rows[ROW_COUNT];
1439            li.className = "show-item";
1440            c1 = li.cells[0];
1441            c1.innerHTML = "plus " + (gMatches.length-ROW_COUNT) + " more";
1442        } else {
1443            list.rows[ROW_COUNT].className = "hide-item";
1444        }*/
1445    //if we have no results, hide the table
1446    } else {
1447        $('#search_filtered_div').addClass('no-display');
1448    }
1449}
1450
1451function search_changed(e, kd, toroot)
1452{
1453    var search = document.getElementById("search_autocomplete");
1454    var text = search.value.replace(/(^ +)|( +$)/g, '');
1455
1456    // show/hide the close button
1457    if (text != '') {
1458        $(".search .close").removeClass("hide");
1459    } else {
1460        $(".search .close").addClass("hide");
1461    }
1462
1463    // 13 = enter
1464    if (e.keyCode == 13) {
1465        $('#search_filtered_div').addClass('no-display');
1466        if (!$('#search_filtered_div').hasClass('no-display') || (gSelectedIndex < 0)) {
1467            if ($("#searchResults").is(":hidden")) {
1468              // if results aren't showing, return true to allow search to execute
1469              return true;
1470            } else {
1471              // otherwise, results are already showing, so allow ajax to auto refresh the results
1472              // and ignore this Enter press to avoid the reload.
1473              return false;
1474            }
1475        } else if (kd && gSelectedIndex >= 0) {
1476            window.location = toroot + gMatches[gSelectedIndex].link;
1477            return false;
1478        }
1479    }
1480    // 38 -- arrow up
1481    else if (kd && (e.keyCode == 38)) {
1482        if (gSelectedIndex >= 0) {
1483            $('#search_filtered li').removeClass('jd-selected');
1484            gSelectedIndex--;
1485            $('#search_filtered li:nth-child('+(gSelectedIndex+1)+')').addClass('jd-selected');
1486        }
1487        return false;
1488    }
1489    // 40 -- arrow down
1490    else if (kd && (e.keyCode == 40)) {
1491        if (gSelectedIndex < gMatches.length-1
1492                        && gSelectedIndex < ROW_COUNT-1) {
1493            $('#search_filtered li').removeClass('jd-selected');
1494            gSelectedIndex++;
1495            $('#search_filtered li:nth-child('+(gSelectedIndex+1)+')').addClass('jd-selected');
1496        }
1497        return false;
1498    }
1499    else if (!kd && (e.keyCode != 40) && (e.keyCode != 38)) {
1500        gMatches = new Array();
1501        matchedCount = 0;
1502        gSelectedIndex = -1;
1503        for (var i=0; i<DATA.length; i++) {
1504            var s = DATA[i];
1505            if (text.length != 0 &&
1506                  s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
1507                gMatches[matchedCount] = s;
1508                matchedCount++;
1509            }
1510        }
1511        rank_autocomplete_results(text);
1512        for (var i=0; i<gMatches.length; i++) {
1513            var s = gMatches[i];
1514            if (gSelectedID == s.id) {
1515                gSelectedIndex = i;
1516            }
1517        }
1518        highlight_autocomplete_result_labels(text);
1519        sync_selection_table(toroot);
1520        return true; // allow the event to bubble up to the search api
1521    }
1522}
1523
1524function rank_autocomplete_results(query) {
1525    query = query || '';
1526    if (!gMatches || !gMatches.length)
1527      return;
1528
1529    // helper function that gets the last occurence index of the given regex
1530    // in the given string, or -1 if not found
1531    var _lastSearch = function(s, re) {
1532      if (s == '')
1533        return -1;
1534      var l = -1;
1535      var tmp;
1536      while ((tmp = s.search(re)) >= 0) {
1537        if (l < 0) l = 0;
1538        l += tmp;
1539        s = s.substr(tmp + 1);
1540      }
1541      return l;
1542    };
1543
1544    // helper function that counts the occurrences of a given character in
1545    // a given string
1546    var _countChar = function(s, c) {
1547      var n = 0;
1548      for (var i=0; i<s.length; i++)
1549        if (s.charAt(i) == c) ++n;
1550      return n;
1551    };
1552
1553    var queryLower = query.toLowerCase();
1554    var queryAlnum = (queryLower.match(/\w+/) || [''])[0];
1555    var partPrefixAlnumRE = new RegExp('\\b' + queryAlnum);
1556    var partExactAlnumRE = new RegExp('\\b' + queryAlnum + '\\b');
1557
1558    var _resultScoreFn = function(result) {
1559        // scores are calculated based on exact and prefix matches,
1560        // and then number of path separators (dots) from the last
1561        // match (i.e. favoring classes and deep package names)
1562        var score = 1.0;
1563        var labelLower = result.label.toLowerCase();
1564        var t;
1565        t = _lastSearch(labelLower, partExactAlnumRE);
1566        if (t >= 0) {
1567            // exact part match
1568            var partsAfter = _countChar(labelLower.substr(t + 1), '.');
1569            score *= 200 / (partsAfter + 1);
1570        } else {
1571            t = _lastSearch(labelLower, partPrefixAlnumRE);
1572            if (t >= 0) {
1573                // part prefix match
1574                var partsAfter = _countChar(labelLower.substr(t + 1), '.');
1575                score *= 20 / (partsAfter + 1);
1576            }
1577        }
1578
1579        return score;
1580    };
1581
1582    for (var i=0; i<gMatches.length; i++) {
1583        gMatches[i].__resultScore = _resultScoreFn(gMatches[i]);
1584    }
1585
1586    gMatches.sort(function(a,b){
1587        var n = b.__resultScore - a.__resultScore;
1588        if (n == 0) // lexicographical sort if scores are the same
1589            n = (a.label < b.label) ? -1 : 1;
1590        return n;
1591    });
1592}
1593
1594function highlight_autocomplete_result_labels(query) {
1595    query = query || '';
1596    if (!gMatches || !gMatches.length)
1597      return;
1598
1599    var queryLower = query.toLowerCase();
1600    var queryAlnumDot = (queryLower.match(/[\w\.]+/) || [''])[0];
1601    var queryRE = new RegExp(
1602        '(' + queryAlnumDot.replace(/\./g, '\\.') + ')', 'ig');
1603    for (var i=0; i<gMatches.length; i++) {
1604        gMatches[i].__hilabel = gMatches[i].label.replace(
1605            queryRE, '<b>$1</b>');
1606    }
1607}
1608
1609function search_focus_changed(obj, focused)
1610{
1611    if (!focused) {
1612        if(obj.value == ""){
1613          $(".search .close").addClass("hide");
1614        }
1615        document.getElementById("search_filtered_div").className = "no-display";
1616    }
1617}
1618
1619function submit_search() {
1620  var query = document.getElementById('search_autocomplete').value;
1621  location.hash = 'q=' + query;
1622  loadSearchResults();
1623  $("#searchResults").slideDown('slow');
1624  return false;
1625}
1626
1627
1628function hideResults() {
1629  $("#searchResults").slideUp();
1630  $(".search .close").addClass("hide");
1631  location.hash = '';
1632
1633  $("#search_autocomplete").val("").blur();
1634
1635  // reset the ajax search callback to nothing, so results don't appear unless ENTER
1636  searchControl.setSearchStartingCallback(this, function(control, searcher, query) {});
1637  return false;
1638}
1639
1640
1641
1642/* ########################################################## */
1643/* ################  CUSTOM SEARCH ENGINE  ################## */
1644/* ########################################################## */
1645
1646google.load('search', '1');
1647var searchControl;
1648
1649function loadSearchResults() {
1650  document.getElementById("search_autocomplete").style.color = "#000";
1651
1652  // create search control
1653  searchControl = new google.search.SearchControl();
1654
1655  // use our existing search form and use tabs when multiple searchers are used
1656  drawOptions = new google.search.DrawOptions();
1657  drawOptions.setDrawMode(google.search.SearchControl.DRAW_MODE_TABBED);
1658  drawOptions.setInput(document.getElementById("search_autocomplete"));
1659
1660  // configure search result options
1661  searchOptions = new google.search.SearcherOptions();
1662  searchOptions.setExpandMode(GSearchControl.EXPAND_MODE_OPEN);
1663
1664  // configure each of the searchers, for each tab
1665  devSiteSearcher = new google.search.WebSearch();
1666  devSiteSearcher.setUserDefinedLabel("All");
1667  devSiteSearcher.setSiteRestriction("001482626316274216503:zu90b7s047u");
1668
1669  designSearcher = new google.search.WebSearch();
1670  designSearcher.setUserDefinedLabel("Design");
1671  designSearcher.setSiteRestriction("http://developer.android.com/design/");
1672
1673  trainingSearcher = new google.search.WebSearch();
1674  trainingSearcher.setUserDefinedLabel("Training");
1675  trainingSearcher.setSiteRestriction("http://developer.android.com/training/");
1676
1677  guidesSearcher = new google.search.WebSearch();
1678  guidesSearcher.setUserDefinedLabel("Guides");
1679  guidesSearcher.setSiteRestriction("http://developer.android.com/guide/");
1680
1681  referenceSearcher = new google.search.WebSearch();
1682  referenceSearcher.setUserDefinedLabel("Reference");
1683  referenceSearcher.setSiteRestriction("http://developer.android.com/reference/");
1684
1685  blogSearcher = new google.search.WebSearch();
1686  blogSearcher.setUserDefinedLabel("Blog");
1687  blogSearcher.setSiteRestriction("http://android-developers.blogspot.com");
1688
1689  // add each searcher to the search control
1690  searchControl.addSearcher(devSiteSearcher, searchOptions);
1691  searchControl.addSearcher(designSearcher, searchOptions);
1692  searchControl.addSearcher(trainingSearcher, searchOptions);
1693  searchControl.addSearcher(guidesSearcher, searchOptions);
1694  searchControl.addSearcher(referenceSearcher, searchOptions);
1695  searchControl.addSearcher(blogSearcher, searchOptions);
1696
1697  // configure result options
1698  searchControl.setResultSetSize(google.search.Search.LARGE_RESULTSET);
1699  searchControl.setLinkTarget(google.search.Search.LINK_TARGET_SELF);
1700  searchControl.setTimeoutInterval(google.search.SearchControl.TIMEOUT_SHORT);
1701  searchControl.setNoResultsString(google.search.SearchControl.NO_RESULTS_DEFAULT_STRING);
1702
1703  // upon ajax search, refresh the url and search title
1704  searchControl.setSearchStartingCallback(this, function(control, searcher, query) {
1705    updateResultTitle(query);
1706    var query = document.getElementById('search_autocomplete').value;
1707    location.hash = 'q=' + query;
1708  });
1709
1710  // draw the search results box
1711  searchControl.draw(document.getElementById("leftSearchControl"), drawOptions);
1712
1713  // get query and execute the search
1714  searchControl.execute(decodeURI(getQuery(location.hash)));
1715
1716  document.getElementById("search_autocomplete").focus();
1717  addTabListeners();
1718}
1719// End of loadSearchResults
1720
1721
1722google.setOnLoadCallback(function(){
1723  if (location.hash.indexOf("q=") == -1) {
1724    // if there's no query in the url, don't search and make sure results are hidden
1725    $('#searchResults').hide();
1726    return;
1727  } else {
1728    // first time loading search results for this page
1729    $('#searchResults').slideDown('slow');
1730    $(".search .close").removeClass("hide");
1731    loadSearchResults();
1732  }
1733}, true);
1734
1735// when an event on the browser history occurs (back, forward, load) requery hash and do search
1736$(window).hashchange( function(){
1737  // Exit if the hash isn't a search query or there's an error in the query
1738  if ((location.hash.indexOf("q=") == -1) || (query == "undefined")) {
1739    // If the results pane is open, close it.
1740    if (!$("#searchResults").is(":hidden")) {
1741      hideResults();
1742    }
1743    return;
1744  }
1745
1746  // Otherwise, we have a search to do
1747  var query = decodeURI(getQuery(location.hash));
1748  searchControl.execute(query);
1749  $('#searchResults').slideDown('slow');
1750  $("#search_autocomplete").focus();
1751  $(".search .close").removeClass("hide");
1752
1753  updateResultTitle(query);
1754});
1755
1756function updateResultTitle(query) {
1757  $("#searchTitle").html("Results for <em>" + escapeHTML(query) + "</em>");
1758}
1759
1760// forcefully regain key-up event control (previously jacked by search api)
1761$("#search_autocomplete").keyup(function(event) {
1762  return search_changed(event, false, toRoot);
1763});
1764
1765// add event listeners to each tab so we can track the browser history
1766function addTabListeners() {
1767  var tabHeaders = $(".gsc-tabHeader");
1768  for (var i = 0; i < tabHeaders.length; i++) {
1769    $(tabHeaders[i]).attr("id",i).click(function() {
1770    /*
1771      // make a copy of the page numbers for the search left pane
1772      setTimeout(function() {
1773        // remove any residual page numbers
1774        $('#searchResults .gsc-tabsArea .gsc-cursor-box.gs-bidi-start-align').remove();
1775        // move the page numbers to the left position; make a clone,
1776        // because the element is drawn to the DOM only once
1777        // and because we're going to remove it (previous line),
1778        // we need it to be available to move again as the user navigates
1779        $('#searchResults .gsc-webResult .gsc-cursor-box.gs-bidi-start-align:visible')
1780                        .clone().appendTo('#searchResults .gsc-tabsArea');
1781        }, 200);
1782      */
1783    });
1784  }
1785  setTimeout(function(){$(tabHeaders[0]).click()},200);
1786}
1787
1788
1789function getQuery(hash) {
1790  var queryParts = hash.split('=');
1791  return queryParts[1];
1792}
1793
1794/* returns the given string with all HTML brackets converted to entities
1795    TODO: move this to the site's JS library */
1796function escapeHTML(string) {
1797  return string.replace(/</g,"&lt;")
1798                .replace(/>/g,"&gt;");
1799}
1800
1801
1802
1803
1804
1805
1806
1807/* ######################################################## */
1808/* #################  JAVADOC REFERENCE ################### */
1809/* ######################################################## */
1810
1811/* Initialize some droiddoc stuff, but only if we're in the reference */
1812if (location.pathname.indexOf("/reference") == 0) {
1813  $(document).ready(function() {
1814    // init available apis based on user pref
1815    changeApiLevel();
1816    initSidenavHeightResize()
1817  });
1818}
1819
1820var API_LEVEL_COOKIE = "api_level";
1821var minLevel = 1;
1822var maxLevel = 1;
1823
1824/******* SIDENAV DIMENSIONS ************/
1825
1826  function initSidenavHeightResize() {
1827    // Change the drag bar size to nicely fit the scrollbar positions
1828    var $dragBar = $(".ui-resizable-s");
1829    $dragBar.css({'width': $dragBar.parent().width() - 5 + "px"});
1830
1831    $( "#resize-packages-nav" ).resizable({
1832      containment: "#nav-panels",
1833      handles: "s",
1834      alsoResize: "#packages-nav",
1835      resize: function(event, ui) { resizeNav(); }, /* resize the nav while dragging */
1836      stop: function(event, ui) { saveNavPanels(); } /* once stopped, save the sizes to cookie  */
1837      });
1838
1839  }
1840
1841function updateSidenavFixedWidth() {
1842  if (!navBarIsFixed) return;
1843  $('#devdoc-nav').css({
1844    'width' : $('#side-nav').css('width'),
1845    'margin' : $('#side-nav').css('margin')
1846  });
1847  $('#devdoc-nav a.totop').css({'display':'block','width':$("#nav").innerWidth()+'px'});
1848
1849  initSidenavHeightResize();
1850}
1851
1852function updateSidenavFullscreenWidth() {
1853  if (!navBarIsFixed) return;
1854  $('#devdoc-nav').css({
1855    'width' : $('#side-nav').css('width'),
1856    'margin' : $('#side-nav').css('margin')
1857  });
1858  $('#devdoc-nav .totop').css({'left': 'inherit'});
1859
1860  initSidenavHeightResize();
1861}
1862
1863function buildApiLevelSelector() {
1864  maxLevel = SINCE_DATA.length;
1865  var userApiLevel = parseInt(readCookie(API_LEVEL_COOKIE));
1866  userApiLevel = userApiLevel == 0 ? maxLevel : userApiLevel; // If there's no cookie (zero), use the max by default
1867
1868  minLevel = parseInt($("#doc-api-level").attr("class"));
1869  // Handle provisional api levels; the provisional level will always be the highest possible level
1870  // Provisional api levels will also have a length; other stuff that's just missing a level won't,
1871  // so leave those kinds of entities at the default level of 1 (for example, the R.styleable class)
1872  if (isNaN(minLevel) && minLevel.length) {
1873    minLevel = maxLevel;
1874  }
1875  var select = $("#apiLevelSelector").html("").change(changeApiLevel);
1876  for (var i = maxLevel-1; i >= 0; i--) {
1877    var option = $("<option />").attr("value",""+SINCE_DATA[i]).append(""+SINCE_DATA[i]);
1878  //  if (SINCE_DATA[i] < minLevel) option.addClass("absent"); // always false for strings (codenames)
1879    select.append(option);
1880  }
1881
1882  // get the DOM element and use setAttribute cuz IE6 fails when using jquery .attr('selected',true)
1883  var selectedLevelItem = $("#apiLevelSelector option[value='"+userApiLevel+"']").get(0);
1884  selectedLevelItem.setAttribute('selected',true);
1885}
1886
1887function changeApiLevel() {
1888  maxLevel = SINCE_DATA.length;
1889  var selectedLevel = maxLevel;
1890
1891  selectedLevel = parseInt($("#apiLevelSelector option:selected").val());
1892  toggleVisisbleApis(selectedLevel, "body");
1893
1894  var date = new Date();
1895  date.setTime(date.getTime()+(10*365*24*60*60*1000)); // keep this for 10 years
1896  var expiration = date.toGMTString();
1897  writeCookie(API_LEVEL_COOKIE, selectedLevel, null, expiration);
1898
1899  if (selectedLevel < minLevel) {
1900    var thing = ($("#jd-header").html().indexOf("package") != -1) ? "package" : "class";
1901    $("#naMessage").show().html("<div><p><strong>This " + thing + " is not available with API level " + selectedLevel + ".</strong></p>"
1902                              + "<p>To use this " + thing + ", you must develop your app using a build target "
1903                              + "that supports API level " + $("#doc-api-level").attr("class") + " or higher. To read these "
1904                              + "APIs, change the value of the API level filter above.</p>"
1905                              + "<p><a href='" +toRoot+ "guide/appendix/api-levels.html'>What is the API level?</a></p></div>");
1906  } else {
1907    $("#naMessage").hide();
1908  }
1909}
1910
1911function toggleVisisbleApis(selectedLevel, context) {
1912  var apis = $(".api",context);
1913  apis.each(function(i) {
1914    var obj = $(this);
1915    var className = obj.attr("class");
1916    var apiLevelIndex = className.lastIndexOf("-")+1;
1917    var apiLevelEndIndex = className.indexOf(" ", apiLevelIndex);
1918    apiLevelEndIndex = apiLevelEndIndex != -1 ? apiLevelEndIndex : className.length;
1919    var apiLevel = className.substring(apiLevelIndex, apiLevelEndIndex);
1920    if (apiLevel.length == 0) { // for odd cases when the since data is actually missing, just bail
1921      return;
1922    }
1923    apiLevel = parseInt(apiLevel);
1924
1925    // Handle provisional api levels; if this item's level is the provisional one, set it to the max
1926    var selectedLevelNum = parseInt(selectedLevel)
1927    var apiLevelNum = parseInt(apiLevel);
1928    if (isNaN(apiLevelNum)) {
1929        apiLevelNum = maxLevel;
1930    }
1931
1932    // Grey things out that aren't available and give a tooltip title
1933    if (apiLevelNum > selectedLevelNum) {
1934      obj.addClass("absent").attr("title","Requires API Level \""
1935            + apiLevel + "\" or higher");
1936    }
1937    else obj.removeClass("absent").removeAttr("title");
1938  });
1939}
1940
1941
1942
1943
1944/* #################  SIDENAV TREE VIEW ################### */
1945
1946function new_node(me, mom, text, link, children_data, api_level)
1947{
1948  var node = new Object();
1949  node.children = Array();
1950  node.children_data = children_data;
1951  node.depth = mom.depth + 1;
1952
1953  node.li = document.createElement("li");
1954  mom.get_children_ul().appendChild(node.li);
1955
1956  node.label_div = document.createElement("div");
1957  node.label_div.className = "label";
1958  if (api_level != null) {
1959    $(node.label_div).addClass("api");
1960    $(node.label_div).addClass("api-level-"+api_level);
1961  }
1962  node.li.appendChild(node.label_div);
1963
1964  if (children_data != null) {
1965    node.expand_toggle = document.createElement("a");
1966    node.expand_toggle.href = "javascript:void(0)";
1967    node.expand_toggle.onclick = function() {
1968          if (node.expanded) {
1969            $(node.get_children_ul()).slideUp("fast");
1970            node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
1971            node.expanded = false;
1972          } else {
1973            expand_node(me, node);
1974          }
1975       };
1976    node.label_div.appendChild(node.expand_toggle);
1977
1978    node.plus_img = document.createElement("img");
1979    node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
1980    node.plus_img.className = "plus";
1981    node.plus_img.width = "8";
1982    node.plus_img.border = "0";
1983    node.expand_toggle.appendChild(node.plus_img);
1984
1985    node.expanded = false;
1986  }
1987
1988  var a = document.createElement("a");
1989  node.label_div.appendChild(a);
1990  node.label = document.createTextNode(text);
1991  a.appendChild(node.label);
1992  if (link) {
1993    a.href = me.toroot + link;
1994  } else {
1995    if (children_data != null) {
1996      a.className = "nolink";
1997      a.href = "javascript:void(0)";
1998      a.onclick = node.expand_toggle.onclick;
1999      // This next line shouldn't be necessary.  I'll buy a beer for the first
2000      // person who figures out how to remove this line and have the link
2001      // toggle shut on the first try. --joeo@android.com
2002      node.expanded = false;
2003    }
2004  }
2005
2006
2007  node.children_ul = null;
2008  node.get_children_ul = function() {
2009      if (!node.children_ul) {
2010        node.children_ul = document.createElement("ul");
2011        node.children_ul.className = "children_ul";
2012        node.children_ul.style.display = "none";
2013        node.li.appendChild(node.children_ul);
2014      }
2015      return node.children_ul;
2016    };
2017
2018  return node;
2019}
2020
2021function expand_node(me, node)
2022{
2023  if (node.children_data && !node.expanded) {
2024    if (node.children_visited) {
2025      $(node.get_children_ul()).slideDown("fast");
2026    } else {
2027      get_node(me, node);
2028      if ($(node.label_div).hasClass("absent")) {
2029        $(node.get_children_ul()).addClass("absent");
2030      }
2031      $(node.get_children_ul()).slideDown("fast");
2032    }
2033    node.plus_img.src = me.toroot + "assets/images/triangle-opened-small.png";
2034    node.expanded = true;
2035
2036    // perform api level toggling because new nodes are new to the DOM
2037    var selectedLevel = $("#apiLevelSelector option:selected").val();
2038    toggleVisisbleApis(selectedLevel, "#side-nav");
2039  }
2040}
2041
2042function get_node(me, mom)
2043{
2044  mom.children_visited = true;
2045  for (var i in mom.children_data) {
2046    var node_data = mom.children_data[i];
2047    mom.children[i] = new_node(me, mom, node_data[0], node_data[1],
2048        node_data[2], node_data[3]);
2049  }
2050}
2051
2052function this_page_relative(toroot)
2053{
2054  var full = document.location.pathname;
2055  var file = "";
2056  if (toroot.substr(0, 1) == "/") {
2057    if (full.substr(0, toroot.length) == toroot) {
2058      return full.substr(toroot.length);
2059    } else {
2060      // the file isn't under toroot.  Fail.
2061      return null;
2062    }
2063  } else {
2064    if (toroot != "./") {
2065      toroot = "./" + toroot;
2066    }
2067    do {
2068      if (toroot.substr(toroot.length-3, 3) == "../" || toroot == "./") {
2069        var pos = full.lastIndexOf("/");
2070        file = full.substr(pos) + file;
2071        full = full.substr(0, pos);
2072        toroot = toroot.substr(0, toroot.length-3);
2073      }
2074    } while (toroot != "" && toroot != "/");
2075    return file.substr(1);
2076  }
2077}
2078
2079function find_page(url, data)
2080{
2081  var nodes = data;
2082  var result = null;
2083  for (var i in nodes) {
2084    var d = nodes[i];
2085    if (d[1] == url) {
2086      return new Array(i);
2087    }
2088    else if (d[2] != null) {
2089      result = find_page(url, d[2]);
2090      if (result != null) {
2091        return (new Array(i).concat(result));
2092      }
2093    }
2094  }
2095  return null;
2096}
2097
2098function load_navtree_data(toroot) {
2099  var navtreeData = document.createElement("script");
2100  navtreeData.setAttribute("type","text/javascript");
2101  navtreeData.setAttribute("src", toroot+"navtree_data.js");
2102  $("head").append($(navtreeData));
2103}
2104
2105function init_default_navtree(toroot) {
2106  init_navtree("tree-list", toroot, NAVTREE_DATA);
2107
2108  // perform api level toggling because because the whole tree is new to the DOM
2109  var selectedLevel = $("#apiLevelSelector option:selected").val();
2110  toggleVisisbleApis(selectedLevel, "#side-nav");
2111}
2112
2113function init_navtree(navtree_id, toroot, root_nodes)
2114{
2115  var me = new Object();
2116  me.toroot = toroot;
2117  me.node = new Object();
2118
2119  me.node.li = document.getElementById(navtree_id);
2120  me.node.children_data = root_nodes;
2121  me.node.children = new Array();
2122  me.node.children_ul = document.createElement("ul");
2123  me.node.get_children_ul = function() { return me.node.children_ul; };
2124  //me.node.children_ul.className = "children_ul";
2125  me.node.li.appendChild(me.node.children_ul);
2126  me.node.depth = 0;
2127
2128  get_node(me, me.node);
2129
2130  me.this_page = this_page_relative(toroot);
2131  me.breadcrumbs = find_page(me.this_page, root_nodes);
2132  if (me.breadcrumbs != null && me.breadcrumbs.length != 0) {
2133    var mom = me.node;
2134    for (var i in me.breadcrumbs) {
2135      var j = me.breadcrumbs[i];
2136      mom = mom.children[j];
2137      expand_node(me, mom);
2138    }
2139    mom.label_div.className = mom.label_div.className + " selected";
2140    addLoadEvent(function() {
2141      scrollIntoView("nav-tree");
2142      });
2143  }
2144}
2145
2146/* TOGGLE INHERITED MEMBERS */
2147
2148/* Toggle an inherited class (arrow toggle)
2149 * @param linkObj  The link that was clicked.
2150 * @param expand  'true' to ensure it's expanded. 'false' to ensure it's closed.
2151 *                'null' to simply toggle.
2152 */
2153function toggleInherited(linkObj, expand) {
2154    var base = linkObj.getAttribute("id");
2155    var list = document.getElementById(base + "-list");
2156    var summary = document.getElementById(base + "-summary");
2157    var trigger = document.getElementById(base + "-trigger");
2158    var a = $(linkObj);
2159    if ( (expand == null && a.hasClass("closed")) || expand ) {
2160        list.style.display = "none";
2161        summary.style.display = "block";
2162        trigger.src = toRoot + "assets/images/triangle-opened.png";
2163        a.removeClass("closed");
2164        a.addClass("opened");
2165    } else if ( (expand == null && a.hasClass("opened")) || (expand == false) ) {
2166        list.style.display = "block";
2167        summary.style.display = "none";
2168        trigger.src = toRoot + "assets/images/triangle-closed.png";
2169        a.removeClass("opened");
2170        a.addClass("closed");
2171    }
2172    return false;
2173}
2174
2175/* Toggle all inherited classes in a single table (e.g. all inherited methods)
2176 * @param linkObj  The link that was clicked.
2177 * @param expand  'true' to ensure it's expanded. 'false' to ensure it's closed.
2178 *                'null' to simply toggle.
2179 */
2180function toggleAllInherited(linkObj, expand) {
2181  var a = $(linkObj);
2182  var table = $(a.parent().parent().parent()); // ugly way to get table/tbody
2183  var expandos = $(".jd-expando-trigger", table);
2184  if ( (expand == null && a.text() == "[Expand]") || expand ) {
2185    expandos.each(function(i) {
2186      toggleInherited(this, true);
2187    });
2188    a.text("[Collapse]");
2189  } else if ( (expand == null && a.text() == "[Collapse]") || (expand == false) ) {
2190    expandos.each(function(i) {
2191      toggleInherited(this, false);
2192    });
2193    a.text("[Expand]");
2194  }
2195  return false;
2196}
2197
2198/* Toggle all inherited members in the class (link in the class title)
2199 */
2200function toggleAllClassInherited() {
2201  var a = $("#toggleAllClassInherited"); // get toggle link from class title
2202  var toggles = $(".toggle-all", $("#body-content"));
2203  if (a.text() == "[Expand All]") {
2204    toggles.each(function(i) {
2205      toggleAllInherited(this, true);
2206    });
2207    a.text("[Collapse All]");
2208  } else {
2209    toggles.each(function(i) {
2210      toggleAllInherited(this, false);
2211    });
2212    a.text("[Expand All]");
2213  }
2214  return false;
2215}
2216
2217/* Expand all inherited members in the class. Used when initiating page search */
2218function ensureAllInheritedExpanded() {
2219  var toggles = $(".toggle-all", $("#body-content"));
2220  toggles.each(function(i) {
2221    toggleAllInherited(this, true);
2222  });
2223  $("#toggleAllClassInherited").text("[Collapse All]");
2224}
2225
2226
2227/* HANDLE KEY EVENTS
2228 * - Listen for Ctrl+F (Cmd on Mac) and expand all inherited members (to aid page search)
2229 */
2230var agent = navigator['userAgent'].toLowerCase();
2231var mac = agent.indexOf("macintosh") != -1;
2232
2233$(document).keydown( function(e) {
2234var control = mac ? e.metaKey && !e.ctrlKey : e.ctrlKey; // get ctrl key
2235  if (control && e.which == 70) {  // 70 is "F"
2236    ensureAllInheritedExpanded();
2237  }
2238});
2239
2240
2241
2242