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