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