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