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