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