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