docs.js revision 698fff028ab6e58c45c7356922beaa7c6a2e9006
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 22$(document).ready(function() { 23 24 // show lang dialog if the URL includes /intl/ 25 //if (location.pathname.substring(0,6) == "/intl/") { 26 // var lang = location.pathname.split('/')[2]; 27 // if (lang != getLangPref()) { 28 // $("#langMessage a.yes").attr("onclick","changeLangPref('" + lang 29 // + "', true); $('#langMessage').hide(); return false;"); 30 // $("#langMessage .lang." + lang).show(); 31 // $("#langMessage").show(); 32 // } 33 //} 34 35 // load json file for JD doc search suggestions 36 $.getScript(toRoot + 'jd_lists_unified.js'); 37 // load json file for Android API search suggestions 38 $.getScript(toRoot + 'reference/lists.js'); 39 // load json files for Google services API suggestions 40 $.getScript(toRoot + 'reference/gcm_lists.js', function(data, textStatus, jqxhr) { 41 // once the GCM json (GCM_DATA) is loaded, load the GMS json (GMS_DATA) and merge the data 42 if(jqxhr.status === 200) { 43 $.getScript(toRoot + 'reference/gms_lists.js', function(data, textStatus, jqxhr) { 44 if(jqxhr.status === 200) { 45 // combine GCM and GMS data 46 GOOGLE_DATA = GMS_DATA; 47 var start = GOOGLE_DATA.length; 48 for (var i=0; i<GCM_DATA.length; i++) { 49 GOOGLE_DATA.push({id:start+i, label:GCM_DATA[i].label, 50 link:GCM_DATA[i].link, type:GCM_DATA[i].type}); 51 } 52 } 53 }); 54 } 55 }); 56 57 // setup keyboard listener for search shortcut 58 $('body').keyup(function(event) { 59 if (event.which == 191) { 60 $('#search_autocomplete').focus(); 61 } 62 }); 63 64 // init the fullscreen toggle click event 65 $('#nav-swap .fullscreen').click(function(){ 66 if ($(this).hasClass('disabled')) { 67 toggleFullscreen(true); 68 } else { 69 toggleFullscreen(false); 70 } 71 }); 72 73 // initialize the divs with custom scrollbars 74 $('.scroll-pane').jScrollPane( {verticalGutter:0} ); 75 76 // add HRs below all H2s (except for a few other h2 variants) 77 $('h2').not('#qv h2') 78 .not('#tb h2') 79 .not('.sidebox h2') 80 .not('#devdoc-nav h2') 81 .not('h2.norule').css({marginBottom:0}) 82 .after('<hr/>'); 83 84 // set up the search close button 85 $('.search .close').click(function() { 86 $searchInput = $('#search_autocomplete'); 87 $searchInput.attr('value', ''); 88 $(this).addClass("hide"); 89 $("#search-container").removeClass('active'); 90 $("#search_autocomplete").blur(); 91 search_focus_changed($searchInput.get(), false); 92 hideResults(); 93 }); 94 95 // Set up quicknav 96 var quicknav_open = false; 97 $("#btn-quicknav").click(function() { 98 if (quicknav_open) { 99 $(this).removeClass('active'); 100 quicknav_open = false; 101 collapse(); 102 } else { 103 $(this).addClass('active'); 104 quicknav_open = true; 105 expand(); 106 } 107 }) 108 109 var expand = function() { 110 $('#header-wrap').addClass('quicknav'); 111 $('#quicknav').stop().show().animate({opacity:'1'}); 112 } 113 114 var collapse = function() { 115 $('#quicknav').stop().animate({opacity:'0'}, 100, function() { 116 $(this).hide(); 117 $('#header-wrap').removeClass('quicknav'); 118 }); 119 } 120 121 122 //Set up search 123 $("#search_autocomplete").focus(function() { 124 $("#search-container").addClass('active'); 125 }) 126 $("#search-container").mouseover(function() { 127 $("#search-container").addClass('active'); 128 $("#search_autocomplete").focus(); 129 }) 130 $("#search-container").mouseout(function() { 131 if ($("#search_autocomplete").is(":focus")) return; 132 if ($("#search_autocomplete").val() == '') { 133 setTimeout(function(){ 134 $("#search-container").removeClass('active'); 135 $("#search_autocomplete").blur(); 136 },250); 137 } 138 }) 139 $("#search_autocomplete").blur(function() { 140 if ($("#search_autocomplete").val() == '') { 141 $("#search-container").removeClass('active'); 142 } 143 }) 144 145 146 // prep nav expandos 147 var pagePath = document.location.pathname; 148 // account for intl docs by removing the intl/*/ path 149 if (pagePath.indexOf("/intl/") == 0) { 150 pagePath = pagePath.substr(pagePath.indexOf("/",6)); // start after intl/ to get last / 151 } 152 153 if (pagePath.indexOf(SITE_ROOT) == 0) { 154 if (pagePath == '' || pagePath.charAt(pagePath.length - 1) == '/') { 155 pagePath += 'index.html'; 156 } 157 } 158 159 // Need a copy of the pagePath before it gets changed in the next block; 160 // it's needed to perform proper tab highlighting in offline docs (see rootDir below) 161 var pagePathOriginal = pagePath; 162 if (SITE_ROOT.match(/\.\.\//) || SITE_ROOT == '') { 163 // If running locally, SITE_ROOT will be a relative path, so account for that by 164 // finding the relative URL to this page. This will allow us to find links on the page 165 // leading back to this page. 166 var pathParts = pagePath.split('/'); 167 var relativePagePathParts = []; 168 var upDirs = (SITE_ROOT.match(/(\.\.\/)+/) || [''])[0].length / 3; 169 for (var i = 0; i < upDirs; i++) { 170 relativePagePathParts.push('..'); 171 } 172 for (var i = 0; i < upDirs; i++) { 173 relativePagePathParts.push(pathParts[pathParts.length - (upDirs - i) - 1]); 174 } 175 relativePagePathParts.push(pathParts[pathParts.length - 1]); 176 pagePath = relativePagePathParts.join('/'); 177 } else { 178 // Otherwise the page path is already an absolute URL 179 } 180 181 // Highlight the header tabs... 182 // highlight Design tab 183 if ($("body").hasClass("design")) { 184 $("#header li.design a").addClass("selected"); 185 $("#sticky-header").addClass("design"); 186 187 // highlight About tabs 188 } else if ($("body").hasClass("about")) { 189 var rootDir = pagePathOriginal.substring(1,pagePathOriginal.indexOf('/', 1)); 190 if (rootDir == "about") { 191 $("#nav-x li.about a").addClass("selected"); 192 } else if (rootDir == "wear") { 193 $("#nav-x li.wear a").addClass("selected"); 194 } else if (rootDir == "tv") { 195 $("#nav-x li.tv a").addClass("selected"); 196 } else if (rootDir == "auto") { 197 $("#nav-x li.auto a").addClass("selected"); 198 } 199 // highlight Develop tab 200 } else if ($("body").hasClass("develop") || $("body").hasClass("google")) { 201 $("#header li.develop a").addClass("selected"); 202 $("#sticky-header").addClass("develop"); 203 // In Develop docs, also highlight appropriate sub-tab 204 var rootDir = pagePathOriginal.substring(1,pagePathOriginal.indexOf('/', 1)); 205 if (rootDir == "training") { 206 $("#nav-x li.training a").addClass("selected"); 207 } else if (rootDir == "guide") { 208 $("#nav-x li.guide a").addClass("selected"); 209 } else if (rootDir == "reference") { 210 // If the root is reference, but page is also part of Google Services, select Google 211 if ($("body").hasClass("google")) { 212 $("#nav-x li.google a").addClass("selected"); 213 } else { 214 $("#nav-x li.reference a").addClass("selected"); 215 } 216 } else if ((rootDir == "tools") || (rootDir == "sdk")) { 217 $("#nav-x li.tools a").addClass("selected"); 218 } else if ($("body").hasClass("google")) { 219 $("#nav-x li.google a").addClass("selected"); 220 } else if ($("body").hasClass("samples")) { 221 $("#nav-x li.samples a").addClass("selected"); 222 } 223 224 // highlight Distribute tab 225 } else if ($("body").hasClass("distribute")) { 226 $("#header li.distribute a").addClass("selected"); 227 $("#sticky-header").addClass("distribute"); 228 229 var baseFrag = pagePathOriginal.indexOf('/', 1) + 1; 230 var secondFrag = pagePathOriginal.substring(baseFrag, pagePathOriginal.indexOf('/', baseFrag)); 231 if (secondFrag == "users") { 232 $("#nav-x li.users a").addClass("selected"); 233 } else if (secondFrag == "engage") { 234 $("#nav-x li.engage a").addClass("selected"); 235 } else if (secondFrag == "monetize") { 236 $("#nav-x li.monetize a").addClass("selected"); 237 } else if (secondFrag == "tools") { 238 $("#nav-x li.disttools a").addClass("selected"); 239 } else if (secondFrag == "stories") { 240 $("#nav-x li.stories a").addClass("selected"); 241 } else if (secondFrag == "essentials") { 242 $("#nav-x li.essentials a").addClass("selected"); 243 } else if (secondFrag == "googleplay") { 244 $("#nav-x li.googleplay a").addClass("selected"); 245 } 246 } else if ($("body").hasClass("about")) { 247 $("#sticky-header").addClass("about"); 248 } 249 250 // set global variable so we can highlight the sidenav a bit later (such as for google reference) 251 // and highlight the sidenav 252 mPagePath = pagePath; 253 highlightSidenav(); 254 buildBreadcrumbs(); 255 256 // set up prev/next links if they exist 257 var $selNavLink = $('#nav').find('a[href="' + pagePath + '"]'); 258 var $selListItem; 259 if ($selNavLink.length) { 260 $selListItem = $selNavLink.closest('li'); 261 262 // set up prev links 263 var $prevLink = []; 264 var $prevListItem = $selListItem.prev('li'); 265 266 var crossBoundaries = ($("body.design").length > 0) || ($("body.guide").length > 0) ? true : 267false; // navigate across topic boundaries only in design docs 268 if ($prevListItem.length) { 269 if ($prevListItem.hasClass('nav-section') || crossBoundaries) { 270 // jump to last topic of previous section 271 $prevLink = $prevListItem.find('a:last'); 272 } else if (!$selListItem.hasClass('nav-section')) { 273 // jump to previous topic in this section 274 $prevLink = $prevListItem.find('a:eq(0)'); 275 } 276 } else { 277 // jump to this section's index page (if it exists) 278 var $parentListItem = $selListItem.parents('li'); 279 $prevLink = $selListItem.parents('li').find('a'); 280 281 // except if cross boundaries aren't allowed, and we're at the top of a section already 282 // (and there's another parent) 283 if (!crossBoundaries && $parentListItem.hasClass('nav-section') 284 && $selListItem.hasClass('nav-section')) { 285 $prevLink = []; 286 } 287 } 288 289 // set up next links 290 var $nextLink = []; 291 var startClass = false; 292 var isCrossingBoundary = false; 293 294 if ($selListItem.hasClass('nav-section') && $selListItem.children('div.empty').length == 0) { 295 // we're on an index page, jump to the first topic 296 $nextLink = $selListItem.find('ul:eq(0)').find('a:eq(0)'); 297 298 // if there aren't any children, go to the next section (required for About pages) 299 if($nextLink.length == 0) { 300 $nextLink = $selListItem.next('li').find('a'); 301 } else if ($('.topic-start-link').length) { 302 // as long as there's a child link and there is a "topic start link" (we're on a landing) 303 // then set the landing page "start link" text to be the first doc title 304 $('.topic-start-link').text($nextLink.text().toUpperCase()); 305 } 306 307 // If the selected page has a description, then it's a class or article homepage 308 if ($selListItem.find('a[description]').length) { 309 // this means we're on a class landing page 310 startClass = true; 311 } 312 } else { 313 // jump to the next topic in this section (if it exists) 314 $nextLink = $selListItem.next('li').find('a:eq(0)'); 315 if ($nextLink.length == 0) { 316 isCrossingBoundary = true; 317 // no more topics in this section, jump to the first topic in the next section 318 $nextLink = $selListItem.parents('li:eq(0)').next('li').find('a:eq(0)'); 319 if (!$nextLink.length) { // Go up another layer to look for next page (lesson > class > course) 320 $nextLink = $selListItem.parents('li:eq(1)').next('li.nav-section').find('a:eq(0)'); 321 if ($nextLink.length == 0) { 322 // if that doesn't work, we're at the end of the list, so disable NEXT link 323 $('.next-page-link').attr('href','').addClass("disabled") 324 .click(function() { return false; }); 325 // and completely hide the one in the footer 326 $('.content-footer .next-page-link').hide(); 327 } 328 } 329 } 330 } 331 332 if (startClass) { 333 $('.start-class-link').attr('href', $nextLink.attr('href')).removeClass("hide"); 334 335 // if there's no training bar (below the start button), 336 // then we need to add a bottom border to button 337 if (!$("#tb").length) { 338 $('.start-class-link').css({'border-bottom':'1px solid #DADADA'}); 339 } 340 } else if (isCrossingBoundary && !$('body.design').length) { // Design always crosses boundaries 341 $('.content-footer.next-class').show(); 342 $('.next-page-link').attr('href','') 343 .removeClass("hide").addClass("disabled") 344 .click(function() { return false; }); 345 // and completely hide the one in the footer 346 $('.content-footer .next-page-link').hide(); 347 if ($nextLink.length) { 348 $('.next-class-link').attr('href',$nextLink.attr('href')) 349 .removeClass("hide") 350 .append(": " + $nextLink.html()); 351 $('.next-class-link').find('.new').empty(); 352 } 353 } else { 354 $('.next-page-link').attr('href', $nextLink.attr('href')) 355 .removeClass("hide"); 356 // for the footer link, also add the next page title 357 $('.content-footer .next-page-link').append(": " + $nextLink.html()); 358 } 359 360 if (!startClass && $prevLink.length) { 361 var prevHref = $prevLink.attr('href'); 362 if (prevHref == SITE_ROOT + 'index.html') { 363 // Don't show Previous when it leads to the homepage 364 } else { 365 $('.prev-page-link').attr('href', $prevLink.attr('href')).removeClass("hide"); 366 } 367 } 368 369 } 370 371 372 373 // Set up the course landing pages for Training with class names and descriptions 374 if ($('body.trainingcourse').length) { 375 var $classLinks = $selListItem.find('ul li a').not('#nav .nav-section .nav-section ul a'); 376 377 // create an array for all the class descriptions 378 var $classDescriptions = new Array($classLinks.length); 379 var lang = getLangPref(); 380 $classLinks.each(function(index) { 381 var langDescr = $(this).attr(lang + "-description"); 382 if (typeof langDescr !== 'undefined' && langDescr !== false) { 383 // if there's a class description in the selected language, use that 384 $classDescriptions[index] = langDescr; 385 } else { 386 // otherwise, use the default english description 387 $classDescriptions[index] = $(this).attr("description"); 388 } 389 }); 390 391 var $olClasses = $('<ol class="class-list"></ol>'); 392 var $liClass; 393 var $imgIcon; 394 var $h2Title; 395 var $pSummary; 396 var $olLessons; 397 var $liLesson; 398 $classLinks.each(function(index) { 399 $liClass = $('<li></li>'); 400 $h2Title = $('<a class="title" href="'+$(this).attr('href')+'"><h2>' + $(this).html()+'</h2><span></span></a>'); 401 $pSummary = $('<p class="description">' + $classDescriptions[index] + '</p>'); 402 403 $olLessons = $('<ol class="lesson-list"></ol>'); 404 405 $lessons = $(this).closest('li').find('ul li a'); 406 407 if ($lessons.length) { 408 $imgIcon = $('<img src="'+toRoot+'assets/images/resource-tutorial.png" ' 409 + ' width="64" height="64" alt=""/>'); 410 $lessons.each(function(index) { 411 $olLessons.append('<li><a href="'+$(this).attr('href')+'">' + $(this).html()+'</a></li>'); 412 }); 413 } else { 414 $imgIcon = $('<img src="'+toRoot+'assets/images/resource-article.png" ' 415 + ' width="64" height="64" alt=""/>'); 416 $pSummary.addClass('article'); 417 } 418 419 $liClass.append($h2Title).append($imgIcon).append($pSummary).append($olLessons); 420 $olClasses.append($liClass); 421 }); 422 $('.jd-descr').append($olClasses); 423 } 424 425 // Set up expand/collapse behavior 426 initExpandableNavItems("#nav"); 427 428 429 $(".scroll-pane").scroll(function(event) { 430 event.preventDefault(); 431 return false; 432 }); 433 434 /* Resize nav height when window height changes */ 435 $(window).resize(function() { 436 if ($('#side-nav').length == 0) return; 437 var stylesheet = $('link[rel="stylesheet"][class="fullscreen"]'); 438 setNavBarLeftPos(); // do this even if sidenav isn't fixed because it could become fixed 439 // make sidenav behave when resizing the window and side-scolling is a concern 440 if (sticky) { 441 if ((stylesheet.attr("disabled") == "disabled") || stylesheet.length == 0) { 442 updateSideNavPosition(); 443 } else { 444 updateSidenavFullscreenWidth(); 445 } 446 } 447 resizeNav(); 448 }); 449 450 451 var navBarLeftPos; 452 if ($('#devdoc-nav').length) { 453 setNavBarLeftPos(); 454 } 455 456 457 // Set up play-on-hover <video> tags. 458 $('video.play-on-hover').bind('click', function(){ 459 $(this).get(0).load(); // in case the video isn't seekable 460 $(this).get(0).play(); 461 }); 462 463 // Set up tooltips 464 var TOOLTIP_MARGIN = 10; 465 $('acronym,.tooltip-link').each(function() { 466 var $target = $(this); 467 var $tooltip = $('<div>') 468 .addClass('tooltip-box') 469 .append($target.attr('title')) 470 .hide() 471 .appendTo('body'); 472 $target.removeAttr('title'); 473 474 $target.hover(function() { 475 // in 476 var targetRect = $target.offset(); 477 targetRect.width = $target.width(); 478 targetRect.height = $target.height(); 479 480 $tooltip.css({ 481 left: targetRect.left, 482 top: targetRect.top + targetRect.height + TOOLTIP_MARGIN 483 }); 484 $tooltip.addClass('below'); 485 $tooltip.show(); 486 }, function() { 487 // out 488 $tooltip.hide(); 489 }); 490 }); 491 492 // Set up <h2> deeplinks 493 $('h2').click(function() { 494 var id = $(this).attr('id'); 495 if (id) { 496 document.location.hash = id; 497 } 498 }); 499 500 //Loads the +1 button 501 var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true; 502 po.src = 'https://apis.google.com/js/plusone.js'; 503 var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s); 504 505 506 // Revise the sidenav widths to make room for the scrollbar 507 // which avoids the visible width from changing each time the bar appears 508 var $sidenav = $("#side-nav"); 509 var sidenav_width = parseInt($sidenav.innerWidth()); 510 511 $("#devdoc-nav #nav").css("width", sidenav_width - 4 + "px"); // 4px is scrollbar width 512 513 514 $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller 515 516 if ($(".scroll-pane").length > 1) { 517 // Check if there's a user preference for the panel heights 518 var cookieHeight = readCookie("reference_height"); 519 if (cookieHeight) { 520 restoreHeight(cookieHeight); 521 } 522 } 523 524 // Resize once loading is finished 525 resizeNav(); 526 // Check if there's an anchor that we need to scroll into view. 527 // A delay is needed, because some browsers do not immediately scroll down to the anchor 528 window.setTimeout(offsetScrollForSticky, 100); 529 530 /* init the language selector based on user cookie for lang */ 531 loadLangPref(); 532 changeNavLang(getLangPref()); 533 534 /* setup event handlers to ensure the overflow menu is visible while picking lang */ 535 $("#language select") 536 .mousedown(function() { 537 $("div.morehover").addClass("hover"); }) 538 .blur(function() { 539 $("div.morehover").removeClass("hover"); }); 540 541 /* some global variable setup */ 542 resizePackagesNav = $("#resize-packages-nav"); 543 classesNav = $("#classes-nav"); 544 devdocNav = $("#devdoc-nav"); 545 546 var cookiePath = ""; 547 if (location.href.indexOf("/reference/") != -1) { 548 cookiePath = "reference_"; 549 } else if (location.href.indexOf("/guide/") != -1) { 550 cookiePath = "guide_"; 551 } else if (location.href.indexOf("/tools/") != -1) { 552 cookiePath = "tools_"; 553 } else if (location.href.indexOf("/training/") != -1) { 554 cookiePath = "training_"; 555 } else if (location.href.indexOf("/design/") != -1) { 556 cookiePath = "design_"; 557 } else if (location.href.indexOf("/distribute/") != -1) { 558 cookiePath = "distribute_"; 559 } 560 561 562 /* setup shadowbox for any videos that want it */ 563 var $videoLinks = $("a.video-shadowbox-button"); 564 if ($videoLinks.length) { 565 // if there's at least one, add the shadowbox HTML to the body 566 $('body').prepend( 567'<div id="video-container">'+ 568 '<div id="video-frame">'+ 569 '<div class="video-close">'+ 570 '<span id="icon-video-close" onclick="closeVideo()"> </span>'+ 571 '</div>'+ 572 '<div id="youTubePlayer"></div>'+ 573 '</div>'+ 574'</div>'); 575 576 // loads the IFrame Player API code asynchronously. 577 $.getScript("https://www.youtube.com/iframe_api"); 578 579 $videoLinks.each(function() { 580 var videoId = $(this).attr('href').split('?v=')[1]; 581 $(this).click(function(event) { 582 event.preventDefault(); 583 startYouTubePlayer(videoId); 584 }); 585 }); 586 587 } 588 589}); 590// END of the onload event 591 592 593var youTubePlayer; 594function onYouTubeIframeAPIReady() { 595} 596 597function startYouTubePlayer(videoId) { 598 if (youTubePlayer == null) { 599 youTubePlayer = new YT.Player('youTubePlayer', { 600 height: '529', 601 width: '940', 602 videoId: videoId, 603 events: { 604 'onReady': onPlayerReady 605 } 606 }); 607 } else { 608 youTubePlayer.playVideo(); 609 } 610 $("#video-container").fadeIn(200, function(){$("#video-frame").show()}); 611} 612 613function onPlayerReady(event) { 614 event.target.playVideo(); 615} 616 617function closeVideo() { 618 try { 619 youTubePlayer.stopVideo(); 620 $("#video-container").fadeOut(200); 621 } catch(e) { 622 console.log('Video not available'); 623 $("#video-container").fadeOut(200); 624 } 625} 626 627 628 629function initExpandableNavItems(rootTag) { 630 $(rootTag + ' li.nav-section .nav-section-header').click(function() { 631 var section = $(this).closest('li.nav-section'); 632 if (section.hasClass('expanded')) { 633 /* hide me and descendants */ 634 section.find('ul').slideUp(250, function() { 635 // remove 'expanded' class from my section and any children 636 section.closest('li').removeClass('expanded'); 637 $('li.nav-section', section).removeClass('expanded'); 638 resizeNav(); 639 }); 640 } else { 641 /* show me */ 642 // first hide all other siblings 643 var $others = $('li.nav-section.expanded', $(this).closest('ul')).not('.sticky'); 644 $others.removeClass('expanded').children('ul').slideUp(250); 645 646 // now expand me 647 section.closest('li').addClass('expanded'); 648 section.children('ul').slideDown(250, function() { 649 resizeNav(); 650 }); 651 } 652 }); 653 654 // Stop expand/collapse behavior when clicking on nav section links 655 // (since we're navigating away from the page) 656 // This selector captures the first instance of <a>, but not those with "#" as the href. 657 $('.nav-section-header').find('a:eq(0)').not('a[href="#"]').click(function(evt) { 658 window.location.href = $(this).attr('href'); 659 return false; 660 }); 661} 662 663 664/** Create the list of breadcrumb links in the sticky header */ 665function buildBreadcrumbs() { 666 var $breadcrumbUl = $("#sticky-header ul.breadcrumb"); 667 // Add the secondary horizontal nav item, if provided 668 var $selectedSecondNav = $("div#nav-x ul.nav-x a.selected").clone().removeClass("selected"); 669 if ($selectedSecondNav.length) { 670 $breadcrumbUl.prepend($("<li>").append($selectedSecondNav)) 671 } 672 // Add the primary horizontal nav 673 var $selectedFirstNav = $("div#header-wrap ul.nav-x a.selected").clone().removeClass("selected"); 674 // If there's no header nav item, use the logo link and title from alt text 675 if ($selectedFirstNav.length < 1) { 676 $selectedFirstNav = $("<a>") 677 .attr('href', $("div#header .logo a").attr('href')) 678 .text($("div#header .logo img").attr('alt')); 679 } 680 $breadcrumbUl.prepend($("<li>").append($selectedFirstNav)); 681} 682 683 684 685/** Highlight the current page in sidenav, expanding children as appropriate */ 686function highlightSidenav() { 687 // if something is already highlighted, undo it. This is for dynamic navigation (Samples index) 688 if ($("ul#nav li.selected").length) { 689 unHighlightSidenav(); 690 } 691 // look for URL in sidenav, including the hash 692 var $selNavLink = $('#nav').find('a[href="' + mPagePath + location.hash + '"]'); 693 694 // If the selNavLink is still empty, look for it without the hash 695 if ($selNavLink.length == 0) { 696 $selNavLink = $('#nav').find('a[href="' + mPagePath + '"]'); 697 } 698 699 var $selListItem; 700 if ($selNavLink.length) { 701 // Find this page's <li> in sidenav and set selected 702 $selListItem = $selNavLink.closest('li'); 703 $selListItem.addClass('selected'); 704 705 // Traverse up the tree and expand all parent nav-sections 706 $selNavLink.parents('li.nav-section').each(function() { 707 $(this).addClass('expanded'); 708 $(this).children('ul').show(); 709 }); 710 } 711} 712 713function unHighlightSidenav() { 714 $("ul#nav li.selected").removeClass("selected"); 715 $('ul#nav li.nav-section.expanded').removeClass('expanded').children('ul').hide(); 716} 717 718function toggleFullscreen(enable) { 719 var delay = 20; 720 var enabled = true; 721 var stylesheet = $('link[rel="stylesheet"][class="fullscreen"]'); 722 if (enable) { 723 // Currently NOT USING fullscreen; enable fullscreen 724 stylesheet.removeAttr('disabled'); 725 $('#nav-swap .fullscreen').removeClass('disabled'); 726 $('#devdoc-nav').css({left:''}); 727 setTimeout(updateSidenavFullscreenWidth,delay); // need to wait a moment for css to switch 728 enabled = true; 729 } else { 730 // Currently USING fullscreen; disable fullscreen 731 stylesheet.attr('disabled', 'disabled'); 732 $('#nav-swap .fullscreen').addClass('disabled'); 733 setTimeout(updateSidenavFixedWidth,delay); // need to wait a moment for css to switch 734 enabled = false; 735 } 736 writeCookie("fullscreen", enabled, null); 737 setNavBarLeftPos(); 738 resizeNav(delay); 739 updateSideNavPosition(); 740 setTimeout(initSidenavHeightResize,delay); 741} 742 743 744function setNavBarLeftPos() { 745 navBarLeftPos = $('#body-content').offset().left; 746} 747 748 749function updateSideNavPosition() { 750 var newLeft = $(window).scrollLeft() - navBarLeftPos; 751 $('#devdoc-nav').css({left: -newLeft}); 752 $('#devdoc-nav .totop').css({left: -(newLeft - parseInt($('#side-nav').css('margin-left')))}); 753} 754 755// TODO: use $(document).ready instead 756function addLoadEvent(newfun) { 757 var current = window.onload; 758 if (typeof window.onload != 'function') { 759 window.onload = newfun; 760 } else { 761 window.onload = function() { 762 current(); 763 newfun(); 764 } 765 } 766} 767 768var agent = navigator['userAgent'].toLowerCase(); 769// If a mobile phone, set flag and do mobile setup 770if ((agent.indexOf("mobile") != -1) || // android, iphone, ipod 771 (agent.indexOf("blackberry") != -1) || 772 (agent.indexOf("webos") != -1) || 773 (agent.indexOf("mini") != -1)) { // opera mini browsers 774 isMobile = true; 775} 776 777 778$(document).ready(function() { 779 $("pre:not(.no-pretty-print)").addClass("prettyprint"); 780 prettyPrint(); 781}); 782 783 784 785 786/* ######### RESIZE THE SIDENAV HEIGHT ########## */ 787 788function resizeNav(delay) { 789 var $nav = $("#devdoc-nav"); 790 var $window = $(window); 791 var navHeight; 792 793 // Get the height of entire window and the total header height. 794 // Then figure out based on scroll position whether the header is visible 795 var windowHeight = $window.height(); 796 var scrollTop = $window.scrollTop(); 797 var headerHeight = $('#header-wrapper').outerHeight(); 798 var headerVisible = scrollTop < stickyTop; 799 800 // get the height of space between nav and top of window. 801 // Could be either margin or top position, depending on whether the nav is fixed. 802 var topMargin = (parseInt($nav.css('margin-top')) || parseInt($nav.css('top'))) + 1; 803 // add 1 for the #side-nav bottom margin 804 805 // Depending on whether the header is visible, set the side nav's height. 806 if (headerVisible) { 807 // The sidenav height grows as the header goes off screen 808 navHeight = windowHeight - (headerHeight - scrollTop) - topMargin; 809 } else { 810 // Once header is off screen, the nav height is almost full window height 811 navHeight = windowHeight - topMargin; 812 } 813 814 815 816 $scrollPanes = $(".scroll-pane"); 817 if ($scrollPanes.length > 1) { 818 // subtract the height of the api level widget and nav swapper from the available nav height 819 navHeight -= ($('#api-nav-header').outerHeight(true) + $('#nav-swap').outerHeight(true)); 820 821 $("#swapper").css({height:navHeight + "px"}); 822 if ($("#nav-tree").is(":visible")) { 823 $("#nav-tree").css({height:navHeight}); 824 } 825 826 var classesHeight = navHeight - parseInt($("#resize-packages-nav").css("height")) - 10 + "px"; 827 //subtract 10px to account for drag bar 828 829 // if the window becomes small enough to make the class panel height 0, 830 // then the package panel should begin to shrink 831 if (parseInt(classesHeight) <= 0) { 832 $("#resize-packages-nav").css({height:navHeight - 10}); //subtract 10px for drag bar 833 $("#packages-nav").css({height:navHeight - 10}); 834 } 835 836 $("#classes-nav").css({'height':classesHeight, 'margin-top':'10px'}); 837 $("#classes-nav .jspContainer").css({height:classesHeight}); 838 839 840 } else { 841 $nav.height(navHeight); 842 } 843 844 if (delay) { 845 updateFromResize = true; 846 delayedReInitScrollbars(delay); 847 } else { 848 reInitScrollbars(); 849 } 850 851} 852 853var updateScrollbars = false; 854var updateFromResize = false; 855 856/* Re-initialize the scrollbars to account for changed nav size. 857 * This method postpones the actual update by a 1/4 second in order to optimize the 858 * scroll performance while the header is still visible, because re-initializing the 859 * scroll panes is an intensive process. 860 */ 861function delayedReInitScrollbars(delay) { 862 // If we're scheduled for an update, but have received another resize request 863 // before the scheduled resize has occured, just ignore the new request 864 // (and wait for the scheduled one). 865 if (updateScrollbars && updateFromResize) { 866 updateFromResize = false; 867 return; 868 } 869 870 // We're scheduled for an update and the update request came from this method's setTimeout 871 if (updateScrollbars && !updateFromResize) { 872 reInitScrollbars(); 873 updateScrollbars = false; 874 } else { 875 updateScrollbars = true; 876 updateFromResize = false; 877 setTimeout('delayedReInitScrollbars()',delay); 878 } 879} 880 881/* Re-initialize the scrollbars to account for changed nav size. */ 882function reInitScrollbars() { 883 var pane = $(".scroll-pane").each(function(){ 884 var api = $(this).data('jsp'); 885 if (!api) { setTimeout(reInitScrollbars,300); return;} 886 api.reinitialise( {verticalGutter:0} ); 887 }); 888 $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller 889} 890 891 892/* Resize the height of the nav panels in the reference, 893 * and save the new size to a cookie */ 894function saveNavPanels() { 895 var basePath = getBaseUri(location.pathname); 896 var section = basePath.substring(1,basePath.indexOf("/",1)); 897 writeCookie("height", resizePackagesNav.css("height"), section); 898} 899 900 901 902function restoreHeight(packageHeight) { 903 $("#resize-packages-nav").height(packageHeight); 904 $("#packages-nav").height(packageHeight); 905 // var classesHeight = navHeight - packageHeight; 906 // $("#classes-nav").css({height:classesHeight}); 907 // $("#classes-nav .jspContainer").css({height:classesHeight}); 908} 909 910 911 912/* ######### END RESIZE THE SIDENAV HEIGHT ########## */ 913 914 915 916 917 918/** Scroll the jScrollPane to make the currently selected item visible 919 This is called when the page finished loading. */ 920function scrollIntoView(nav) { 921 var $nav = $("#"+nav); 922 var element = $nav.jScrollPane({/* ...settings... */}); 923 var api = element.data('jsp'); 924 925 if ($nav.is(':visible')) { 926 var $selected = $(".selected", $nav); 927 if ($selected.length == 0) { 928 // If no selected item found, exit 929 return; 930 } 931 // get the selected item's offset from its container nav by measuring the item's offset 932 // relative to the document then subtract the container nav's offset relative to the document 933 var selectedOffset = $selected.offset().top - $nav.offset().top; 934 if (selectedOffset > $nav.height() * .8) { // multiply nav height by .8 so we move up the item 935 // if it's more than 80% down the nav 936 // scroll the item up by an amount equal to 80% the container nav's height 937 api.scrollTo(0, selectedOffset - ($nav.height() * .8), false); 938 } 939 } 940} 941 942 943 944 945 946 947/* Show popup dialogs */ 948function showDialog(id) { 949 $dialog = $("#"+id); 950 $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>'); 951 $dialog.wrapInner('<div/>'); 952 $dialog.removeClass("hide"); 953} 954 955 956 957 958 959/* ######### COOKIES! ########## */ 960 961function readCookie(cookie) { 962 var myCookie = cookie_namespace+"_"+cookie+"="; 963 if (document.cookie) { 964 var index = document.cookie.indexOf(myCookie); 965 if (index != -1) { 966 var valStart = index + myCookie.length; 967 var valEnd = document.cookie.indexOf(";", valStart); 968 if (valEnd == -1) { 969 valEnd = document.cookie.length; 970 } 971 var val = document.cookie.substring(valStart, valEnd); 972 return val; 973 } 974 } 975 return 0; 976} 977 978function writeCookie(cookie, val, section) { 979 if (val==undefined) return; 980 section = section == null ? "_" : "_"+section+"_"; 981 var age = 2*365*24*60*60; // set max-age to 2 years 982 var cookieValue = cookie_namespace + section + cookie + "=" + val 983 + "; max-age=" + age +"; path=/"; 984 document.cookie = cookieValue; 985} 986 987/* ######### END COOKIES! ########## */ 988 989 990var sticky = false; 991var stickyTop; 992var prevScrollLeft = 0; // used to compare current position to previous position of horiz scroll 993/* Sets the vertical scoll position at which the sticky bar should appear. 994 This method is called to reset the position when search results appear or hide */ 995function setStickyTop() { 996 stickyTop = $('#header-wrapper').outerHeight() - $('#sticky-header').outerHeight(); 997} 998 999/* 1000 * Displays sticky nav bar on pages when dac header scrolls out of view 1001 */ 1002$(window).scroll(function(event) { 1003 1004 setStickyTop(); 1005 var hiding = false; 1006 var $stickyEl = $('#sticky-header'); 1007 var $menuEl = $('.menu-container'); 1008 // Exit if there's no sidenav 1009 if ($('#side-nav').length == 0) return; 1010 // Exit if the mouse target is a DIV, because that means the event is coming 1011 // from a scrollable div and so there's no need to make adjustments to our layout 1012 if ($(event.target).nodeName == "DIV") { 1013 return; 1014 } 1015 1016 var top = $(window).scrollTop(); 1017 // we set the navbar fixed when the scroll position is beyond the height of the site header... 1018 var shouldBeSticky = top >= stickyTop; 1019 // ... except if the document content is shorter than the sidenav height. 1020 // (this is necessary to avoid crazy behavior on OSX Lion due to overscroll bouncing) 1021 if ($("#doc-col").height() < $("#side-nav").height()) { 1022 shouldBeSticky = false; 1023 } 1024 // Account for horizontal scroll 1025 var scrollLeft = $(window).scrollLeft(); 1026 // When the sidenav is fixed and user scrolls horizontally, reposition the sidenav to match 1027 if (sticky && (scrollLeft != prevScrollLeft)) { 1028 updateSideNavPosition(); 1029 prevScrollLeft = scrollLeft; 1030 } 1031 1032 // Don't continue if the header is sufficently far away 1033 // (to avoid intensive resizing that slows scrolling) 1034 if (sticky == shouldBeSticky) { 1035 return; 1036 } 1037 1038 // If sticky header visible and position is now near top, hide sticky 1039 if (sticky && !shouldBeSticky) { 1040 sticky = false; 1041 hiding = true; 1042 // make the sidenav static again 1043 $('#devdoc-nav') 1044 .removeClass('fixed') 1045 .css({'width':'auto','margin':''}) 1046 .prependTo('#side-nav'); 1047 // delay hide the sticky 1048 $menuEl.removeClass('sticky-menu'); 1049 $stickyEl.fadeOut(250); 1050 hiding = false; 1051 1052 // update the sidenaav position for side scrolling 1053 updateSideNavPosition(); 1054 } else if (!sticky && shouldBeSticky) { 1055 sticky = true; 1056 $stickyEl.fadeIn(10); 1057 $menuEl.addClass('sticky-menu'); 1058 1059 // make the sidenav fixed 1060 var width = $('#devdoc-nav').width(); 1061 $('#devdoc-nav') 1062 .addClass('fixed') 1063 .css({'width':width+'px'}) 1064 .prependTo('#body-content'); 1065 1066 // update the sidenaav position for side scrolling 1067 updateSideNavPosition(); 1068 1069 } else if (hiding && top < 15) { 1070 $menuEl.removeClass('sticky-menu'); 1071 $stickyEl.hide(); 1072 hiding = false; 1073 } 1074 resizeNav(250); // pass true in order to delay the scrollbar re-initialization for performance 1075}); 1076 1077/* 1078 * Manages secion card states and nav resize to conclude loading 1079 */ 1080(function() { 1081 $(document).ready(function() { 1082 1083 // Stack hover states 1084 $('.section-card-menu').each(function(index, el) { 1085 var height = $(el).height(); 1086 $(el).css({height:height+'px', position:'relative'}); 1087 var $cardInfo = $(el).find('.card-info'); 1088 1089 $cardInfo.css({position: 'absolute', bottom:'0px', left:'0px', right:'0px', overflow:'visible'}); 1090 }); 1091 1092 }); 1093 1094})(); 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109/* MISC LIBRARY FUNCTIONS */ 1110 1111 1112 1113 1114 1115function toggle(obj, slide) { 1116 var ul = $("ul:first", obj); 1117 var li = ul.parent(); 1118 if (li.hasClass("closed")) { 1119 if (slide) { 1120 ul.slideDown("fast"); 1121 } else { 1122 ul.show(); 1123 } 1124 li.removeClass("closed"); 1125 li.addClass("open"); 1126 $(".toggle-img", li).attr("title", "hide pages"); 1127 } else { 1128 ul.slideUp("fast"); 1129 li.removeClass("open"); 1130 li.addClass("closed"); 1131 $(".toggle-img", li).attr("title", "show pages"); 1132 } 1133} 1134 1135 1136function buildToggleLists() { 1137 $(".toggle-list").each( 1138 function(i) { 1139 $("div:first", this).append("<a class='toggle-img' href='#' title='show pages' onClick='toggle(this.parentNode.parentNode, true); return false;'></a>"); 1140 $(this).addClass("closed"); 1141 }); 1142} 1143 1144 1145 1146function hideNestedItems(list, toggle) { 1147 $list = $(list); 1148 // hide nested lists 1149 if($list.hasClass('showing')) { 1150 $("li ol", $list).hide('fast'); 1151 $list.removeClass('showing'); 1152 // show nested lists 1153 } else { 1154 $("li ol", $list).show('fast'); 1155 $list.addClass('showing'); 1156 } 1157 $(".more,.less",$(toggle)).toggle(); 1158} 1159 1160 1161/* Call this to add listeners to a <select> element for Studio/Eclipse/Other docs */ 1162function setupIdeDocToggle() { 1163 $( "select.ide" ).change(function() { 1164 var selected = $(this).find("option:selected").attr("value"); 1165 $(".select-ide").hide(); 1166 $(".select-ide."+selected).show(); 1167 1168 $("select.ide").val(selected); 1169 }); 1170} 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195/* REFERENCE NAV SWAP */ 1196 1197 1198function getNavPref() { 1199 var v = readCookie('reference_nav'); 1200 if (v != NAV_PREF_TREE) { 1201 v = NAV_PREF_PANELS; 1202 } 1203 return v; 1204} 1205 1206function chooseDefaultNav() { 1207 nav_pref = getNavPref(); 1208 if (nav_pref == NAV_PREF_TREE) { 1209 $("#nav-panels").toggle(); 1210 $("#panel-link").toggle(); 1211 $("#nav-tree").toggle(); 1212 $("#tree-link").toggle(); 1213 } 1214} 1215 1216function swapNav() { 1217 if (nav_pref == NAV_PREF_TREE) { 1218 nav_pref = NAV_PREF_PANELS; 1219 } else { 1220 nav_pref = NAV_PREF_TREE; 1221 init_default_navtree(toRoot); 1222 } 1223 writeCookie("nav", nav_pref, "reference"); 1224 1225 $("#nav-panels").toggle(); 1226 $("#panel-link").toggle(); 1227 $("#nav-tree").toggle(); 1228 $("#tree-link").toggle(); 1229 1230 resizeNav(); 1231 1232 // Gross nasty hack to make tree view show up upon first swap by setting height manually 1233 $("#nav-tree .jspContainer:visible") 1234 .css({'height':$("#nav-tree .jspContainer .jspPane").height() +'px'}); 1235 // Another nasty hack to make the scrollbar appear now that we have height 1236 resizeNav(); 1237 1238 if ($("#nav-tree").is(':visible')) { 1239 scrollIntoView("nav-tree"); 1240 } else { 1241 scrollIntoView("packages-nav"); 1242 scrollIntoView("classes-nav"); 1243 } 1244} 1245 1246 1247 1248/* ############################################ */ 1249/* ########## LOCALIZATION ############ */ 1250/* ############################################ */ 1251 1252function getBaseUri(uri) { 1253 var intlUrl = (uri.substring(0,6) == "/intl/"); 1254 if (intlUrl) { 1255 base = uri.substring(uri.indexOf('intl/')+5,uri.length); 1256 base = base.substring(base.indexOf('/')+1, base.length); 1257 //alert("intl, returning base url: /" + base); 1258 return ("/" + base); 1259 } else { 1260 //alert("not intl, returning uri as found."); 1261 return uri; 1262 } 1263} 1264 1265function requestAppendHL(uri) { 1266//append "?hl=<lang> to an outgoing request (such as to blog) 1267 var lang = getLangPref(); 1268 if (lang) { 1269 var q = 'hl=' + lang; 1270 uri += '?' + q; 1271 window.location = uri; 1272 return false; 1273 } else { 1274 return true; 1275 } 1276} 1277 1278 1279function changeNavLang(lang) { 1280 var $links = $("#devdoc-nav,#header,#nav-x,.training-nav-top,.content-footer").find("a["+lang+"-lang]"); 1281 $links.each(function(i){ // for each link with a translation 1282 var $link = $(this); 1283 if (lang != "en") { // No need to worry about English, because a language change invokes new request 1284 // put the desired language from the attribute as the text 1285 $link.text($link.attr(lang+"-lang")) 1286 } 1287 }); 1288} 1289 1290function changeLangPref(lang, submit) { 1291 writeCookie("pref_lang", lang, null); 1292 1293 // ####### TODO: Remove this condition once we're stable on devsite ####### 1294 // This condition is only needed if we still need to support legacy GAE server 1295 if (devsite) { 1296 // Switch language when on Devsite server 1297 if (submit) { 1298 $("#setlang").submit(); 1299 } 1300 } else { 1301 // Switch language when on legacy GAE server 1302 if (submit) { 1303 window.location = getBaseUri(location.pathname); 1304 } 1305 } 1306} 1307 1308function loadLangPref() { 1309 var lang = readCookie("pref_lang"); 1310 if (lang != 0) { 1311 $("#language").find("option[value='"+lang+"']").attr("selected",true); 1312 } 1313} 1314 1315function getLangPref() { 1316 var lang = $("#language").find(":selected").attr("value"); 1317 if (!lang) { 1318 lang = readCookie("pref_lang"); 1319 } 1320 return (lang != 0) ? lang : 'en'; 1321} 1322 1323/* ########## END LOCALIZATION ############ */ 1324 1325 1326 1327 1328 1329 1330/* Used to hide and reveal supplemental content, such as long code samples. 1331 See the companion CSS in android-developer-docs.css */ 1332function toggleContent(obj) { 1333 var div = $(obj).closest(".toggle-content"); 1334 var toggleMe = $(".toggle-content-toggleme:eq(0)",div); 1335 if (div.hasClass("closed")) { // if it's closed, open it 1336 toggleMe.slideDown(); 1337 $(".toggle-content-text:eq(0)", obj).toggle(); 1338 div.removeClass("closed").addClass("open"); 1339 $(".toggle-content-img:eq(0)", div).attr("title", "hide").attr("src", toRoot 1340 + "assets/images/triangle-opened.png"); 1341 } else { // if it's open, close it 1342 toggleMe.slideUp('fast', function() { // Wait until the animation is done before closing arrow 1343 $(".toggle-content-text:eq(0)", obj).toggle(); 1344 div.removeClass("open").addClass("closed"); 1345 div.find(".toggle-content").removeClass("open").addClass("closed") 1346 .find(".toggle-content-toggleme").hide(); 1347 $(".toggle-content-img", div).attr("title", "show").attr("src", toRoot 1348 + "assets/images/triangle-closed.png"); 1349 }); 1350 } 1351 return false; 1352} 1353 1354 1355/* New version of expandable content */ 1356function toggleExpandable(link,id) { 1357 if($(id).is(':visible')) { 1358 $(id).slideUp(); 1359 $(link).removeClass('expanded'); 1360 } else { 1361 $(id).slideDown(); 1362 $(link).addClass('expanded'); 1363 } 1364} 1365 1366function hideExpandable(ids) { 1367 $(ids).slideUp(); 1368 $(ids).prev('h4').find('a.expandable').removeClass('expanded'); 1369} 1370 1371 1372 1373 1374 1375/* 1376 * Slideshow 1.0 1377 * Used on /index.html and /develop/index.html for carousel 1378 * 1379 * Sample usage: 1380 * HTML - 1381 * <div class="slideshow-container"> 1382 * <a href="" class="slideshow-prev">Prev</a> 1383 * <a href="" class="slideshow-next">Next</a> 1384 * <ul> 1385 * <li class="item"><img src="images/marquee1.jpg"></li> 1386 * <li class="item"><img src="images/marquee2.jpg"></li> 1387 * <li class="item"><img src="images/marquee3.jpg"></li> 1388 * <li class="item"><img src="images/marquee4.jpg"></li> 1389 * </ul> 1390 * </div> 1391 * 1392 * <script type="text/javascript"> 1393 * $('.slideshow-container').dacSlideshow({ 1394 * auto: true, 1395 * btnPrev: '.slideshow-prev', 1396 * btnNext: '.slideshow-next' 1397 * }); 1398 * </script> 1399 * 1400 * Options: 1401 * btnPrev: optional identifier for previous button 1402 * btnNext: optional identifier for next button 1403 * btnPause: optional identifier for pause button 1404 * auto: whether or not to auto-proceed 1405 * speed: animation speed 1406 * autoTime: time between auto-rotation 1407 * easing: easing function for transition 1408 * start: item to select by default 1409 * scroll: direction to scroll in 1410 * pagination: whether or not to include dotted pagination 1411 * 1412 */ 1413 1414 (function($) { 1415 $.fn.dacSlideshow = function(o) { 1416 1417 //Options - see above 1418 o = $.extend({ 1419 btnPrev: null, 1420 btnNext: null, 1421 btnPause: null, 1422 auto: true, 1423 speed: 500, 1424 autoTime: 12000, 1425 easing: null, 1426 start: 0, 1427 scroll: 1, 1428 pagination: true 1429 1430 }, o || {}); 1431 1432 //Set up a carousel for each 1433 return this.each(function() { 1434 1435 var running = false; 1436 var animCss = o.vertical ? "top" : "left"; 1437 var sizeCss = o.vertical ? "height" : "width"; 1438 var div = $(this); 1439 var ul = $("ul", div); 1440 var tLi = $("li", ul); 1441 var tl = tLi.size(); 1442 var timer = null; 1443 1444 var li = $("li", ul); 1445 var itemLength = li.size(); 1446 var curr = o.start; 1447 1448 li.css({float: o.vertical ? "none" : "left"}); 1449 ul.css({margin: "0", padding: "0", position: "relative", "list-style-type": "none", "z-index": "1"}); 1450 div.css({position: "relative", "z-index": "2", left: "0px"}); 1451 1452 var liSize = o.vertical ? height(li) : width(li); 1453 var ulSize = liSize * itemLength; 1454 var divSize = liSize; 1455 1456 li.css({width: li.width(), height: li.height()}); 1457 ul.css(sizeCss, ulSize+"px").css(animCss, -(curr*liSize)); 1458 1459 div.css(sizeCss, divSize+"px"); 1460 1461 //Pagination 1462 if (o.pagination) { 1463 var pagination = $("<div class='pagination'></div>"); 1464 var pag_ul = $("<ul></ul>"); 1465 if (tl > 1) { 1466 for (var i=0;i<tl;i++) { 1467 var li = $("<li>"+i+"</li>"); 1468 pag_ul.append(li); 1469 if (i==o.start) li.addClass('active'); 1470 li.click(function() { 1471 go(parseInt($(this).text())); 1472 }) 1473 } 1474 pagination.append(pag_ul); 1475 div.append(pagination); 1476 } 1477 } 1478 1479 //Previous button 1480 if(o.btnPrev) 1481 $(o.btnPrev).click(function(e) { 1482 e.preventDefault(); 1483 return go(curr-o.scroll); 1484 }); 1485 1486 //Next button 1487 if(o.btnNext) 1488 $(o.btnNext).click(function(e) { 1489 e.preventDefault(); 1490 return go(curr+o.scroll); 1491 }); 1492 1493 //Pause button 1494 if(o.btnPause) 1495 $(o.btnPause).click(function(e) { 1496 e.preventDefault(); 1497 if ($(this).hasClass('paused')) { 1498 startRotateTimer(); 1499 } else { 1500 pauseRotateTimer(); 1501 } 1502 }); 1503 1504 //Auto rotation 1505 if(o.auto) startRotateTimer(); 1506 1507 function startRotateTimer() { 1508 clearInterval(timer); 1509 timer = setInterval(function() { 1510 if (curr == tl-1) { 1511 go(0); 1512 } else { 1513 go(curr+o.scroll); 1514 } 1515 }, o.autoTime); 1516 $(o.btnPause).removeClass('paused'); 1517 } 1518 1519 function pauseRotateTimer() { 1520 clearInterval(timer); 1521 $(o.btnPause).addClass('paused'); 1522 } 1523 1524 //Go to an item 1525 function go(to) { 1526 if(!running) { 1527 1528 if(to<0) { 1529 to = itemLength-1; 1530 } else if (to>itemLength-1) { 1531 to = 0; 1532 } 1533 curr = to; 1534 1535 running = true; 1536 1537 ul.animate( 1538 animCss == "left" ? { left: -(curr*liSize) } : { top: -(curr*liSize) } , o.speed, o.easing, 1539 function() { 1540 running = false; 1541 } 1542 ); 1543 1544 $(o.btnPrev + "," + o.btnNext).removeClass("disabled"); 1545 $( (curr-o.scroll<0 && o.btnPrev) 1546 || 1547 (curr+o.scroll > itemLength && o.btnNext) 1548 || 1549 [] 1550 ).addClass("disabled"); 1551 1552 1553 var nav_items = $('li', pagination); 1554 nav_items.removeClass('active'); 1555 nav_items.eq(to).addClass('active'); 1556 1557 1558 } 1559 if(o.auto) startRotateTimer(); 1560 return false; 1561 }; 1562 }); 1563 }; 1564 1565 function css(el, prop) { 1566 return parseInt($.css(el[0], prop)) || 0; 1567 }; 1568 function width(el) { 1569 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight'); 1570 }; 1571 function height(el) { 1572 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom'); 1573 }; 1574 1575 })(jQuery); 1576 1577 1578/* 1579 * dacSlideshow 1.0 1580 * Used on develop/index.html for side-sliding tabs 1581 * 1582 * Sample usage: 1583 * HTML - 1584 * <div class="slideshow-container"> 1585 * <a href="" class="slideshow-prev">Prev</a> 1586 * <a href="" class="slideshow-next">Next</a> 1587 * <ul> 1588 * <li class="item"><img src="images/marquee1.jpg"></li> 1589 * <li class="item"><img src="images/marquee2.jpg"></li> 1590 * <li class="item"><img src="images/marquee3.jpg"></li> 1591 * <li class="item"><img src="images/marquee4.jpg"></li> 1592 * </ul> 1593 * </div> 1594 * 1595 * <script type="text/javascript"> 1596 * $('.slideshow-container').dacSlideshow({ 1597 * auto: true, 1598 * btnPrev: '.slideshow-prev', 1599 * btnNext: '.slideshow-next' 1600 * }); 1601 * </script> 1602 * 1603 * Options: 1604 * btnPrev: optional identifier for previous button 1605 * btnNext: optional identifier for next button 1606 * auto: whether or not to auto-proceed 1607 * speed: animation speed 1608 * autoTime: time between auto-rotation 1609 * easing: easing function for transition 1610 * start: item to select by default 1611 * scroll: direction to scroll in 1612 * pagination: whether or not to include dotted pagination 1613 * 1614 */ 1615 (function($) { 1616 $.fn.dacTabbedList = function(o) { 1617 1618 //Options - see above 1619 o = $.extend({ 1620 speed : 250, 1621 easing: null, 1622 nav_id: null, 1623 frame_id: null 1624 }, o || {}); 1625 1626 //Set up a carousel for each 1627 return this.each(function() { 1628 1629 var curr = 0; 1630 var running = false; 1631 var animCss = "margin-left"; 1632 var sizeCss = "width"; 1633 var div = $(this); 1634 1635 var nav = $(o.nav_id, div); 1636 var nav_li = $("li", nav); 1637 var nav_size = nav_li.size(); 1638 var frame = div.find(o.frame_id); 1639 var content_width = $(frame).find('ul').width(); 1640 //Buttons 1641 $(nav_li).click(function(e) { 1642 go($(nav_li).index($(this))); 1643 }) 1644 1645 //Go to an item 1646 function go(to) { 1647 if(!running) { 1648 curr = to; 1649 running = true; 1650 1651 frame.animate({ 'margin-left' : -(curr*content_width) }, o.speed, o.easing, 1652 function() { 1653 running = false; 1654 } 1655 ); 1656 1657 1658 nav_li.removeClass('active'); 1659 nav_li.eq(to).addClass('active'); 1660 1661 1662 } 1663 return false; 1664 }; 1665 }); 1666 }; 1667 1668 function css(el, prop) { 1669 return parseInt($.css(el[0], prop)) || 0; 1670 }; 1671 function width(el) { 1672 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight'); 1673 }; 1674 function height(el) { 1675 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom'); 1676 }; 1677 1678 })(jQuery); 1679 1680 1681 1682 1683 1684/* ######################################################## */ 1685/* ################ SEARCH SUGGESTIONS ################## */ 1686/* ######################################################## */ 1687 1688 1689 1690var gSelectedIndex = -1; // the index position of currently highlighted suggestion 1691var gSelectedColumn = -1; // which column of suggestion lists is currently focused 1692 1693var gMatches = new Array(); 1694var gLastText = ""; 1695var gInitialized = false; 1696var ROW_COUNT_FRAMEWORK = 20; // max number of results in list 1697var gListLength = 0; 1698 1699 1700var gGoogleMatches = new Array(); 1701var ROW_COUNT_GOOGLE = 15; // max number of results in list 1702var gGoogleListLength = 0; 1703 1704var gDocsMatches = new Array(); 1705var ROW_COUNT_DOCS = 100; // max number of results in list 1706var gDocsListLength = 0; 1707 1708function onSuggestionClick(link) { 1709 // When user clicks a suggested document, track it 1710 ga('send', 'event', 'Suggestion Click', 'clicked: ' + $(link).text(), 1711 'from: ' + $("#search_autocomplete").val()); 1712} 1713 1714function set_item_selected($li, selected) 1715{ 1716 if (selected) { 1717 $li.attr('class','jd-autocomplete jd-selected'); 1718 } else { 1719 $li.attr('class','jd-autocomplete'); 1720 } 1721} 1722 1723function set_item_values(toroot, $li, match) 1724{ 1725 var $link = $('a',$li); 1726 $link.html(match.__hilabel || match.label); 1727 $link.attr('href',toroot + match.link); 1728} 1729 1730function set_item_values_jd(toroot, $li, match) 1731{ 1732 var $link = $('a',$li); 1733 $link.html(match.title); 1734 $link.attr('href',toroot + match.url); 1735} 1736 1737function new_suggestion($list) { 1738 var $li = $("<li class='jd-autocomplete'></li>"); 1739 $list.append($li); 1740 1741 $li.mousedown(function() { 1742 window.location = this.firstChild.getAttribute("href"); 1743 }); 1744 $li.mouseover(function() { 1745 $('.search_filtered_wrapper li').removeClass('jd-selected'); 1746 $(this).addClass('jd-selected'); 1747 gSelectedColumn = $(".search_filtered:visible").index($(this).closest('.search_filtered')); 1748 gSelectedIndex = $("li", $(".search_filtered:visible")[gSelectedColumn]).index(this); 1749 }); 1750 $li.append("<a onclick='onSuggestionClick(this)'></a>"); 1751 $li.attr('class','show-item'); 1752 return $li; 1753} 1754 1755function sync_selection_table(toroot) 1756{ 1757 var $li; //list item jquery object 1758 var i; //list item iterator 1759 1760 // if there are NO results at all, hide all columns 1761 if (!(gMatches.length > 0) && !(gGoogleMatches.length > 0) && !(gDocsMatches.length > 0)) { 1762 $('.suggest-card').hide(300); 1763 return; 1764 } 1765 1766 // if there are api results 1767 if ((gMatches.length > 0) || (gGoogleMatches.length > 0)) { 1768 // reveal suggestion list 1769 $('.suggest-card.dummy').show(); 1770 $('.suggest-card.reference').show(); 1771 var listIndex = 0; // list index position 1772 1773 // reset the lists 1774 $(".search_filtered_wrapper.reference li").remove(); 1775 1776 // ########### ANDROID RESULTS ############# 1777 if (gMatches.length > 0) { 1778 1779 // determine android results to show 1780 gListLength = gMatches.length < ROW_COUNT_FRAMEWORK ? 1781 gMatches.length : ROW_COUNT_FRAMEWORK; 1782 for (i=0; i<gListLength; i++) { 1783 var $li = new_suggestion($(".suggest-card.reference ul")); 1784 set_item_values(toroot, $li, gMatches[i]); 1785 set_item_selected($li, i == gSelectedIndex); 1786 } 1787 } 1788 1789 // ########### GOOGLE RESULTS ############# 1790 if (gGoogleMatches.length > 0) { 1791 // show header for list 1792 $(".suggest-card.reference ul").append("<li class='header'>in Google Services:</li>"); 1793 1794 // determine google results to show 1795 gGoogleListLength = gGoogleMatches.length < ROW_COUNT_GOOGLE ? gGoogleMatches.length : ROW_COUNT_GOOGLE; 1796 for (i=0; i<gGoogleListLength; i++) { 1797 var $li = new_suggestion($(".suggest-card.reference ul")); 1798 set_item_values(toroot, $li, gGoogleMatches[i]); 1799 set_item_selected($li, i == gSelectedIndex); 1800 } 1801 } 1802 } else { 1803 $('.suggest-card.reference').hide(); 1804 $('.suggest-card.dummy').hide(); 1805 } 1806 1807 // ########### JD DOC RESULTS ############# 1808 if (gDocsMatches.length > 0) { 1809 // reset the lists 1810 $(".search_filtered_wrapper.docs li").remove(); 1811 1812 // determine google results to show 1813 // NOTE: The order of the conditions below for the sugg.type MUST BE SPECIFIC: 1814 // The order must match the reverse order that each section appears as a card in 1815 // the suggestion UI... this may be only for the "develop" grouped items though. 1816 gDocsListLength = gDocsMatches.length < ROW_COUNT_DOCS ? gDocsMatches.length : ROW_COUNT_DOCS; 1817 for (i=0; i<gDocsListLength; i++) { 1818 var sugg = gDocsMatches[i]; 1819 var $li; 1820 if (sugg.type == "design") { 1821 $li = new_suggestion($(".suggest-card.design ul")); 1822 } else 1823 if (sugg.type == "distribute") { 1824 $li = new_suggestion($(".suggest-card.distribute ul")); 1825 } else 1826 if (sugg.type == "samples") { 1827 $li = new_suggestion($(".suggest-card.develop .child-card.samples")); 1828 } else 1829 if (sugg.type == "training") { 1830 $li = new_suggestion($(".suggest-card.develop .child-card.training")); 1831 } else 1832 if (sugg.type == "about"||"guide"||"tools"||"google") { 1833 $li = new_suggestion($(".suggest-card.develop .child-card.guides")); 1834 } else { 1835 continue; 1836 } 1837 1838 set_item_values_jd(toroot, $li, sugg); 1839 set_item_selected($li, i == gSelectedIndex); 1840 } 1841 1842 // add heading and show or hide card 1843 if ($(".suggest-card.design li").length > 0) { 1844 $(".suggest-card.design ul").prepend("<li class='header'>Design:</li>"); 1845 $(".suggest-card.design").show(300); 1846 } else { 1847 $('.suggest-card.design').hide(300); 1848 } 1849 if ($(".suggest-card.distribute li").length > 0) { 1850 $(".suggest-card.distribute ul").prepend("<li class='header'>Distribute:</li>"); 1851 $(".suggest-card.distribute").show(300); 1852 } else { 1853 $('.suggest-card.distribute').hide(300); 1854 } 1855 if ($(".child-card.guides li").length > 0) { 1856 $(".child-card.guides").prepend("<li class='header'>Guides:</li>"); 1857 $(".child-card.guides li").appendTo(".suggest-card.develop ul"); 1858 } 1859 if ($(".child-card.training li").length > 0) { 1860 $(".child-card.training").prepend("<li class='header'>Training:</li>"); 1861 $(".child-card.training li").appendTo(".suggest-card.develop ul"); 1862 } 1863 if ($(".child-card.samples li").length > 0) { 1864 $(".child-card.samples").prepend("<li class='header'>Samples:</li>"); 1865 $(".child-card.samples li").appendTo(".suggest-card.develop ul"); 1866 } 1867 1868 if ($(".suggest-card.develop li").length > 0) { 1869 $(".suggest-card.develop").show(300); 1870 } else { 1871 $('.suggest-card.develop').hide(300); 1872 } 1873 1874 } else { 1875 $('.search_filtered_wrapper.docs .suggest-card:not(.dummy)').hide(300); 1876 } 1877} 1878 1879/** Called by the search input's onkeydown and onkeyup events. 1880 * Handles navigation with keyboard arrows, Enter key to invoke search, 1881 * otherwise invokes search suggestions on key-up event. 1882 * @param e The JS event 1883 * @param kd True if the event is key-down 1884 * @param toroot A string for the site's root path 1885 * @returns True if the event should bubble up 1886 */ 1887function search_changed(e, kd, toroot) 1888{ 1889 var currentLang = getLangPref(); 1890 var search = document.getElementById("search_autocomplete"); 1891 var text = search.value.replace(/(^ +)|( +$)/g, ''); 1892 // get the ul hosting the currently selected item 1893 gSelectedColumn = gSelectedColumn >= 0 ? gSelectedColumn : 0; 1894 var $columns = $(".search_filtered_wrapper").find(".search_filtered:visible"); 1895 var $selectedUl = $columns[gSelectedColumn]; 1896 1897 // show/hide the close button 1898 if (text != '') { 1899 $(".search .close").removeClass("hide"); 1900 } else { 1901 $(".search .close").addClass("hide"); 1902 } 1903 // 27 = esc 1904 if (e.keyCode == 27) { 1905 // close all search results 1906 if (kd) $('.search .close').trigger('click'); 1907 return true; 1908 } 1909 // 13 = enter 1910 else if (e.keyCode == 13) { 1911 if (gSelectedIndex < 0) { 1912 $('.suggest-card').hide(); 1913 if ($("#searchResults").is(":hidden") && (search.value != "")) { 1914 // if results aren't showing (and text not empty), return true to allow search to execute 1915 $('body,html').animate({scrollTop:0}, '500', 'swing'); 1916 return true; 1917 } else { 1918 // otherwise, results are already showing, so allow ajax to auto refresh the results 1919 // and ignore this Enter press to avoid the reload. 1920 return false; 1921 } 1922 } else if (kd && gSelectedIndex >= 0) { 1923 // click the link corresponding to selected item 1924 $("a",$("li",$selectedUl)[gSelectedIndex]).get()[0].click(); 1925 return false; 1926 } 1927 } 1928 // If Google results are showing, return true to allow ajax search to execute 1929 else if ($("#searchResults").is(":visible")) { 1930 // Also, if search_results is scrolled out of view, scroll to top to make results visible 1931 if ((sticky ) && (search.value != "")) { 1932 $('body,html').animate({scrollTop:0}, '500', 'swing'); 1933 } 1934 return true; 1935 } 1936 // 38 UP ARROW 1937 else if (kd && (e.keyCode == 38)) { 1938 // if the next item is a header, skip it 1939 if ($($("li", $selectedUl)[gSelectedIndex-1]).hasClass("header")) { 1940 gSelectedIndex--; 1941 } 1942 if (gSelectedIndex >= 0) { 1943 $('li', $selectedUl).removeClass('jd-selected'); 1944 gSelectedIndex--; 1945 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected'); 1946 // If user reaches top, reset selected column 1947 if (gSelectedIndex < 0) { 1948 gSelectedColumn = -1; 1949 } 1950 } 1951 return false; 1952 } 1953 // 40 DOWN ARROW 1954 else if (kd && (e.keyCode == 40)) { 1955 // if the next item is a header, skip it 1956 if ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header")) { 1957 gSelectedIndex++; 1958 } 1959 if ((gSelectedIndex < $("li", $selectedUl).length-1) || 1960 ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header"))) { 1961 $('li', $selectedUl).removeClass('jd-selected'); 1962 gSelectedIndex++; 1963 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected'); 1964 } 1965 return false; 1966 } 1967 // Consider left/right arrow navigation 1968 // NOTE: Order of suggest columns are reverse order (index position 0 is on right) 1969 else if (kd && $columns.length > 1 && gSelectedColumn >= 0) { 1970 // 37 LEFT ARROW 1971 // go left only if current column is not left-most column (last column) 1972 if (e.keyCode == 37 && gSelectedColumn < $columns.length - 1) { 1973 $('li', $selectedUl).removeClass('jd-selected'); 1974 gSelectedColumn++; 1975 $selectedUl = $columns[gSelectedColumn]; 1976 // keep or reset the selected item to last item as appropriate 1977 gSelectedIndex = gSelectedIndex > 1978 $("li", $selectedUl).length-1 ? 1979 $("li", $selectedUl).length-1 : gSelectedIndex; 1980 // if the corresponding item is a header, move down 1981 if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) { 1982 gSelectedIndex++; 1983 } 1984 // set item selected 1985 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected'); 1986 return false; 1987 } 1988 // 39 RIGHT ARROW 1989 // go right only if current column is not the right-most column (first column) 1990 else if (e.keyCode == 39 && gSelectedColumn > 0) { 1991 $('li', $selectedUl).removeClass('jd-selected'); 1992 gSelectedColumn--; 1993 $selectedUl = $columns[gSelectedColumn]; 1994 // keep or reset the selected item to last item as appropriate 1995 gSelectedIndex = gSelectedIndex > 1996 $("li", $selectedUl).length-1 ? 1997 $("li", $selectedUl).length-1 : gSelectedIndex; 1998 // if the corresponding item is a header, move down 1999 if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) { 2000 gSelectedIndex++; 2001 } 2002 // set item selected 2003 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected'); 2004 return false; 2005 } 2006 } 2007 2008 // if key-up event and not arrow down/up/left/right, 2009 // read the search query and add suggestions to gMatches 2010 else if (!kd && (e.keyCode != 40) 2011 && (e.keyCode != 38) 2012 && (e.keyCode != 37) 2013 && (e.keyCode != 39)) { 2014 gSelectedIndex = -1; 2015 gMatches = new Array(); 2016 matchedCount = 0; 2017 gGoogleMatches = new Array(); 2018 matchedCountGoogle = 0; 2019 gDocsMatches = new Array(); 2020 matchedCountDocs = 0; 2021 2022 // Search for Android matches 2023 for (var i=0; i<DATA.length; i++) { 2024 var s = DATA[i]; 2025 if (text.length != 0 && 2026 s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) { 2027 gMatches[matchedCount] = s; 2028 matchedCount++; 2029 } 2030 } 2031 rank_autocomplete_api_results(text, gMatches); 2032 for (var i=0; i<gMatches.length; i++) { 2033 var s = gMatches[i]; 2034 } 2035 2036 2037 // Search for Google matches 2038 for (var i=0; i<GOOGLE_DATA.length; i++) { 2039 var s = GOOGLE_DATA[i]; 2040 if (text.length != 0 && 2041 s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) { 2042 gGoogleMatches[matchedCountGoogle] = s; 2043 matchedCountGoogle++; 2044 } 2045 } 2046 rank_autocomplete_api_results(text, gGoogleMatches); 2047 for (var i=0; i<gGoogleMatches.length; i++) { 2048 var s = gGoogleMatches[i]; 2049 } 2050 2051 highlight_autocomplete_result_labels(text); 2052 2053 2054 2055 // Search for matching JD docs 2056 if (text.length >= 2) { 2057 // Regex to match only the beginning of a word 2058 var textRegex = new RegExp("\\b" + text.toLowerCase(), "g"); 2059 2060 2061 // Search for Training classes 2062 for (var i=0; i<TRAINING_RESOURCES.length; i++) { 2063 // current search comparison, with counters for tag and title, 2064 // used later to improve ranking 2065 var s = TRAINING_RESOURCES[i]; 2066 s.matched_tag = 0; 2067 s.matched_title = 0; 2068 var matched = false; 2069 2070 // Check if query matches any tags; work backwards toward 1 to assist ranking 2071 for (var j = s.keywords.length - 1; j >= 0; j--) { 2072 // it matches a tag 2073 if (s.keywords[j].toLowerCase().match(textRegex)) { 2074 matched = true; 2075 s.matched_tag = j + 1; // add 1 to index position 2076 } 2077 } 2078 // Don't consider doc title for lessons (only for class landing pages), 2079 // unless the lesson has a tag that already matches 2080 if ((s.lang == currentLang) && 2081 (!(s.type == "training" && s.url.indexOf("index.html") == -1) || matched)) { 2082 // it matches the doc title 2083 if (s.title.toLowerCase().match(textRegex)) { 2084 matched = true; 2085 s.matched_title = 1; 2086 } 2087 } 2088 if (matched) { 2089 gDocsMatches[matchedCountDocs] = s; 2090 matchedCountDocs++; 2091 } 2092 } 2093 2094 2095 // Search for API Guides 2096 for (var i=0; i<GUIDE_RESOURCES.length; i++) { 2097 // current search comparison, with counters for tag and title, 2098 // used later to improve ranking 2099 var s = GUIDE_RESOURCES[i]; 2100 s.matched_tag = 0; 2101 s.matched_title = 0; 2102 var matched = false; 2103 2104 // Check if query matches any tags; work backwards toward 1 to assist ranking 2105 for (var j = s.keywords.length - 1; j >= 0; j--) { 2106 // it matches a tag 2107 if (s.keywords[j].toLowerCase().match(textRegex)) { 2108 matched = true; 2109 s.matched_tag = j + 1; // add 1 to index position 2110 } 2111 } 2112 // Check if query matches the doc title, but only for current language 2113 if (s.lang == currentLang) { 2114 // if query matches the doc title 2115 if (s.title.toLowerCase().match(textRegex)) { 2116 matched = true; 2117 s.matched_title = 1; 2118 } 2119 } 2120 if (matched) { 2121 gDocsMatches[matchedCountDocs] = s; 2122 matchedCountDocs++; 2123 } 2124 } 2125 2126 2127 // Search for Tools Guides 2128 for (var i=0; i<TOOLS_RESOURCES.length; i++) { 2129 // current search comparison, with counters for tag and title, 2130 // used later to improve ranking 2131 var s = TOOLS_RESOURCES[i]; 2132 s.matched_tag = 0; 2133 s.matched_title = 0; 2134 var matched = false; 2135 2136 // Check if query matches any tags; work backwards toward 1 to assist ranking 2137 for (var j = s.keywords.length - 1; j >= 0; j--) { 2138 // it matches a tag 2139 if (s.keywords[j].toLowerCase().match(textRegex)) { 2140 matched = true; 2141 s.matched_tag = j + 1; // add 1 to index position 2142 } 2143 } 2144 // Check if query matches the doc title, but only for current language 2145 if (s.lang == currentLang) { 2146 // if query matches the doc title 2147 if (s.title.toLowerCase().match(textRegex)) { 2148 matched = true; 2149 s.matched_title = 1; 2150 } 2151 } 2152 if (matched) { 2153 gDocsMatches[matchedCountDocs] = s; 2154 matchedCountDocs++; 2155 } 2156 } 2157 2158 2159 // Search for About docs 2160 for (var i=0; i<ABOUT_RESOURCES.length; i++) { 2161 // current search comparison, with counters for tag and title, 2162 // used later to improve ranking 2163 var s = ABOUT_RESOURCES[i]; 2164 s.matched_tag = 0; 2165 s.matched_title = 0; 2166 var matched = false; 2167 2168 // Check if query matches any tags; work backwards toward 1 to assist ranking 2169 for (var j = s.keywords.length - 1; j >= 0; j--) { 2170 // it matches a tag 2171 if (s.keywords[j].toLowerCase().match(textRegex)) { 2172 matched = true; 2173 s.matched_tag = j + 1; // add 1 to index position 2174 } 2175 } 2176 // Check if query matches the doc title, but only for current language 2177 if (s.lang == currentLang) { 2178 // if query matches the doc title 2179 if (s.title.toLowerCase().match(textRegex)) { 2180 matched = true; 2181 s.matched_title = 1; 2182 } 2183 } 2184 if (matched) { 2185 gDocsMatches[matchedCountDocs] = s; 2186 matchedCountDocs++; 2187 } 2188 } 2189 2190 2191 // Search for Design guides 2192 for (var i=0; i<DESIGN_RESOURCES.length; i++) { 2193 // current search comparison, with counters for tag and title, 2194 // used later to improve ranking 2195 var s = DESIGN_RESOURCES[i]; 2196 s.matched_tag = 0; 2197 s.matched_title = 0; 2198 var matched = false; 2199 2200 // Check if query matches any tags; work backwards toward 1 to assist ranking 2201 for (var j = s.keywords.length - 1; j >= 0; j--) { 2202 // it matches a tag 2203 if (s.keywords[j].toLowerCase().match(textRegex)) { 2204 matched = true; 2205 s.matched_tag = j + 1; // add 1 to index position 2206 } 2207 } 2208 // Check if query matches the doc title, but only for current language 2209 if (s.lang == currentLang) { 2210 // if query matches the doc title 2211 if (s.title.toLowerCase().match(textRegex)) { 2212 matched = true; 2213 s.matched_title = 1; 2214 } 2215 } 2216 if (matched) { 2217 gDocsMatches[matchedCountDocs] = s; 2218 matchedCountDocs++; 2219 } 2220 } 2221 2222 2223 // Search for Distribute guides 2224 for (var i=0; i<DISTRIBUTE_RESOURCES.length; i++) { 2225 // current search comparison, with counters for tag and title, 2226 // used later to improve ranking 2227 var s = DISTRIBUTE_RESOURCES[i]; 2228 s.matched_tag = 0; 2229 s.matched_title = 0; 2230 var matched = false; 2231 2232 // Check if query matches any tags; work backwards toward 1 to assist ranking 2233 for (var j = s.keywords.length - 1; j >= 0; j--) { 2234 // it matches a tag 2235 if (s.keywords[j].toLowerCase().match(textRegex)) { 2236 matched = true; 2237 s.matched_tag = j + 1; // add 1 to index position 2238 } 2239 } 2240 // Check if query matches the doc title, but only for current language 2241 if (s.lang == currentLang) { 2242 // if query matches the doc title 2243 if (s.title.toLowerCase().match(textRegex)) { 2244 matched = true; 2245 s.matched_title = 1; 2246 } 2247 } 2248 if (matched) { 2249 gDocsMatches[matchedCountDocs] = s; 2250 matchedCountDocs++; 2251 } 2252 } 2253 2254 2255 // Search for Google guides 2256 for (var i=0; i<GOOGLE_RESOURCES.length; i++) { 2257 // current search comparison, with counters for tag and title, 2258 // used later to improve ranking 2259 var s = GOOGLE_RESOURCES[i]; 2260 s.matched_tag = 0; 2261 s.matched_title = 0; 2262 var matched = false; 2263 2264 // Check if query matches any tags; work backwards toward 1 to assist ranking 2265 for (var j = s.keywords.length - 1; j >= 0; j--) { 2266 // it matches a tag 2267 if (s.keywords[j].toLowerCase().match(textRegex)) { 2268 matched = true; 2269 s.matched_tag = j + 1; // add 1 to index position 2270 } 2271 } 2272 // Check if query matches the doc title, but only for current language 2273 if (s.lang == currentLang) { 2274 // if query matches the doc title 2275 if (s.title.toLowerCase().match(textRegex)) { 2276 matched = true; 2277 s.matched_title = 1; 2278 } 2279 } 2280 if (matched) { 2281 gDocsMatches[matchedCountDocs] = s; 2282 matchedCountDocs++; 2283 } 2284 } 2285 2286 2287 // Search for Samples 2288 for (var i=0; i<SAMPLES_RESOURCES.length; i++) { 2289 // current search comparison, with counters for tag and title, 2290 // used later to improve ranking 2291 var s = SAMPLES_RESOURCES[i]; 2292 s.matched_tag = 0; 2293 s.matched_title = 0; 2294 var matched = false; 2295 // Check if query matches any tags; work backwards toward 1 to assist ranking 2296 for (var j = s.keywords.length - 1; j >= 0; j--) { 2297 // it matches a tag 2298 if (s.keywords[j].toLowerCase().match(textRegex)) { 2299 matched = true; 2300 s.matched_tag = j + 1; // add 1 to index position 2301 } 2302 } 2303 // Check if query matches the doc title, but only for current language 2304 if (s.lang == currentLang) { 2305 // if query matches the doc title.t 2306 if (s.title.toLowerCase().match(textRegex)) { 2307 matched = true; 2308 s.matched_title = 1; 2309 } 2310 } 2311 if (matched) { 2312 gDocsMatches[matchedCountDocs] = s; 2313 matchedCountDocs++; 2314 } 2315 } 2316 2317 // Rank/sort all the matched pages 2318 rank_autocomplete_doc_results(text, gDocsMatches); 2319 } 2320 2321 // draw the suggestions 2322 sync_selection_table(toroot); 2323 return true; // allow the event to bubble up to the search api 2324 } 2325} 2326 2327/* Order the jd doc result list based on match quality */ 2328function rank_autocomplete_doc_results(query, matches) { 2329 query = query || ''; 2330 if (!matches || !matches.length) 2331 return; 2332 2333 var _resultScoreFn = function(match) { 2334 var score = 1.0; 2335 2336 // if the query matched a tag 2337 if (match.matched_tag > 0) { 2338 // multiply score by factor relative to position in tags list (max of 3) 2339 score *= 3 / match.matched_tag; 2340 2341 // if it also matched the title 2342 if (match.matched_title > 0) { 2343 score *= 2; 2344 } 2345 } else if (match.matched_title > 0) { 2346 score *= 3; 2347 } 2348 2349 return score; 2350 }; 2351 2352 for (var i=0; i<matches.length; i++) { 2353 matches[i].__resultScore = _resultScoreFn(matches[i]); 2354 } 2355 2356 matches.sort(function(a,b){ 2357 var n = b.__resultScore - a.__resultScore; 2358 if (n == 0) // lexicographical sort if scores are the same 2359 n = (a.label < b.label) ? -1 : 1; 2360 return n; 2361 }); 2362} 2363 2364/* Order the result list based on match quality */ 2365function rank_autocomplete_api_results(query, matches) { 2366 query = query || ''; 2367 if (!matches || !matches.length) 2368 return; 2369 2370 // helper function that gets the last occurence index of the given regex 2371 // in the given string, or -1 if not found 2372 var _lastSearch = function(s, re) { 2373 if (s == '') 2374 return -1; 2375 var l = -1; 2376 var tmp; 2377 while ((tmp = s.search(re)) >= 0) { 2378 if (l < 0) l = 0; 2379 l += tmp; 2380 s = s.substr(tmp + 1); 2381 } 2382 return l; 2383 }; 2384 2385 // helper function that counts the occurrences of a given character in 2386 // a given string 2387 var _countChar = function(s, c) { 2388 var n = 0; 2389 for (var i=0; i<s.length; i++) 2390 if (s.charAt(i) == c) ++n; 2391 return n; 2392 }; 2393 2394 var queryLower = query.toLowerCase(); 2395 var queryAlnum = (queryLower.match(/\w+/) || [''])[0]; 2396 var partPrefixAlnumRE = new RegExp('\\b' + queryAlnum); 2397 var partExactAlnumRE = new RegExp('\\b' + queryAlnum + '\\b'); 2398 2399 var _resultScoreFn = function(result) { 2400 // scores are calculated based on exact and prefix matches, 2401 // and then number of path separators (dots) from the last 2402 // match (i.e. favoring classes and deep package names) 2403 var score = 1.0; 2404 var labelLower = result.label.toLowerCase(); 2405 var t; 2406 t = _lastSearch(labelLower, partExactAlnumRE); 2407 if (t >= 0) { 2408 // exact part match 2409 var partsAfter = _countChar(labelLower.substr(t + 1), '.'); 2410 score *= 200 / (partsAfter + 1); 2411 } else { 2412 t = _lastSearch(labelLower, partPrefixAlnumRE); 2413 if (t >= 0) { 2414 // part prefix match 2415 var partsAfter = _countChar(labelLower.substr(t + 1), '.'); 2416 score *= 20 / (partsAfter + 1); 2417 } 2418 } 2419 2420 return score; 2421 }; 2422 2423 for (var i=0; i<matches.length; i++) { 2424 // if the API is deprecated, default score is 0; otherwise, perform scoring 2425 if (matches[i].deprecated == "true") { 2426 matches[i].__resultScore = 0; 2427 } else { 2428 matches[i].__resultScore = _resultScoreFn(matches[i]); 2429 } 2430 } 2431 2432 matches.sort(function(a,b){ 2433 var n = b.__resultScore - a.__resultScore; 2434 if (n == 0) // lexicographical sort if scores are the same 2435 n = (a.label < b.label) ? -1 : 1; 2436 return n; 2437 }); 2438} 2439 2440/* Add emphasis to part of string that matches query */ 2441function highlight_autocomplete_result_labels(query) { 2442 query = query || ''; 2443 if ((!gMatches || !gMatches.length) && (!gGoogleMatches || !gGoogleMatches.length)) 2444 return; 2445 2446 var queryLower = query.toLowerCase(); 2447 var queryAlnumDot = (queryLower.match(/[\w\.]+/) || [''])[0]; 2448 var queryRE = new RegExp( 2449 '(' + queryAlnumDot.replace(/\./g, '\\.') + ')', 'ig'); 2450 for (var i=0; i<gMatches.length; i++) { 2451 gMatches[i].__hilabel = gMatches[i].label.replace( 2452 queryRE, '<b>$1</b>'); 2453 } 2454 for (var i=0; i<gGoogleMatches.length; i++) { 2455 gGoogleMatches[i].__hilabel = gGoogleMatches[i].label.replace( 2456 queryRE, '<b>$1</b>'); 2457 } 2458} 2459 2460function search_focus_changed(obj, focused) 2461{ 2462 if (!focused) { 2463 if(obj.value == ""){ 2464 $(".search .close").addClass("hide"); 2465 } 2466 $(".suggest-card").hide(); 2467 } 2468} 2469 2470function submit_search() { 2471 var query = document.getElementById('search_autocomplete').value; 2472 location.hash = 'q=' + query; 2473 loadSearchResults(); 2474 $("#searchResults").slideDown('slow', setStickyTop); 2475 return false; 2476} 2477 2478 2479function hideResults() { 2480 $("#searchResults").slideUp('fast', setStickyTop); 2481 $(".search .close").addClass("hide"); 2482 location.hash = ''; 2483 2484 $("#search_autocomplete").val("").blur(); 2485 2486 // reset the ajax search callback to nothing, so results don't appear unless ENTER 2487 searchControl.setSearchStartingCallback(this, function(control, searcher, query) {}); 2488 2489 // forcefully regain key-up event control (previously jacked by search api) 2490 $("#search_autocomplete").keyup(function(event) { 2491 return search_changed(event, false, toRoot); 2492 }); 2493 2494 return false; 2495} 2496 2497 2498 2499/* ########################################################## */ 2500/* ################ CUSTOM SEARCH ENGINE ################## */ 2501/* ########################################################## */ 2502 2503var searchControl; 2504google.load('search', '1', {"callback" : function() { 2505 searchControl = new google.search.SearchControl(); 2506 } }); 2507 2508function loadSearchResults() { 2509 document.getElementById("search_autocomplete").style.color = "#000"; 2510 2511 searchControl = new google.search.SearchControl(); 2512 2513 // use our existing search form and use tabs when multiple searchers are used 2514 drawOptions = new google.search.DrawOptions(); 2515 drawOptions.setDrawMode(google.search.SearchControl.DRAW_MODE_TABBED); 2516 drawOptions.setInput(document.getElementById("search_autocomplete")); 2517 2518 // configure search result options 2519 searchOptions = new google.search.SearcherOptions(); 2520 searchOptions.setExpandMode(GSearchControl.EXPAND_MODE_OPEN); 2521 2522 // configure each of the searchers, for each tab 2523 devSiteSearcher = new google.search.WebSearch(); 2524 devSiteSearcher.setUserDefinedLabel("All"); 2525 devSiteSearcher.setSiteRestriction("001482626316274216503:zu90b7s047u"); 2526 2527 designSearcher = new google.search.WebSearch(); 2528 designSearcher.setUserDefinedLabel("Design"); 2529 designSearcher.setSiteRestriction("http://developer.android.com/design/"); 2530 2531 trainingSearcher = new google.search.WebSearch(); 2532 trainingSearcher.setUserDefinedLabel("Training"); 2533 trainingSearcher.setSiteRestriction("http://developer.android.com/training/"); 2534 2535 guidesSearcher = new google.search.WebSearch(); 2536 guidesSearcher.setUserDefinedLabel("Guides"); 2537 guidesSearcher.setSiteRestriction("http://developer.android.com/guide/"); 2538 2539 referenceSearcher = new google.search.WebSearch(); 2540 referenceSearcher.setUserDefinedLabel("Reference"); 2541 referenceSearcher.setSiteRestriction("http://developer.android.com/reference/"); 2542 2543 googleSearcher = new google.search.WebSearch(); 2544 googleSearcher.setUserDefinedLabel("Google Services"); 2545 googleSearcher.setSiteRestriction("http://developer.android.com/google/"); 2546 2547 blogSearcher = new google.search.WebSearch(); 2548 blogSearcher.setUserDefinedLabel("Blog"); 2549 blogSearcher.setSiteRestriction("http://android-developers.blogspot.com"); 2550 2551 // add each searcher to the search control 2552 searchControl.addSearcher(devSiteSearcher, searchOptions); 2553 searchControl.addSearcher(designSearcher, searchOptions); 2554 searchControl.addSearcher(trainingSearcher, searchOptions); 2555 searchControl.addSearcher(guidesSearcher, searchOptions); 2556 searchControl.addSearcher(referenceSearcher, searchOptions); 2557 searchControl.addSearcher(googleSearcher, searchOptions); 2558 searchControl.addSearcher(blogSearcher, searchOptions); 2559 2560 // configure result options 2561 searchControl.setResultSetSize(google.search.Search.LARGE_RESULTSET); 2562 searchControl.setLinkTarget(google.search.Search.LINK_TARGET_SELF); 2563 searchControl.setTimeoutInterval(google.search.SearchControl.TIMEOUT_SHORT); 2564 searchControl.setNoResultsString(google.search.SearchControl.NO_RESULTS_DEFAULT_STRING); 2565 2566 // upon ajax search, refresh the url and search title 2567 searchControl.setSearchStartingCallback(this, function(control, searcher, query) { 2568 updateResultTitle(query); 2569 var query = document.getElementById('search_autocomplete').value; 2570 location.hash = 'q=' + query; 2571 }); 2572 2573 // once search results load, set up click listeners 2574 searchControl.setSearchCompleteCallback(this, function(control, searcher, query) { 2575 addResultClickListeners(); 2576 }); 2577 2578 // draw the search results box 2579 searchControl.draw(document.getElementById("leftSearchControl"), drawOptions); 2580 2581 // get query and execute the search 2582 searchControl.execute(decodeURI(getQuery(location.hash))); 2583 2584 document.getElementById("search_autocomplete").focus(); 2585 addTabListeners(); 2586} 2587// End of loadSearchResults 2588 2589 2590google.setOnLoadCallback(function(){ 2591 if (location.hash.indexOf("q=") == -1) { 2592 // if there's no query in the url, don't search and make sure results are hidden 2593 $('#searchResults').hide(); 2594 return; 2595 } else { 2596 // first time loading search results for this page 2597 $('#searchResults').slideDown('slow', setStickyTop); 2598 $(".search .close").removeClass("hide"); 2599 loadSearchResults(); 2600 } 2601}, true); 2602 2603/* Adjust the scroll position to account for sticky header, only if the hash matches an id. 2604 This does not handle <a name=""> tags. Some CSS fixes those, but only for reference docs. */ 2605function offsetScrollForSticky() { 2606 // Ignore if there's no search bar (some special pages have no header) 2607 if ($("#search-container").length < 1) return; 2608 2609 var hash = escape(location.hash.substr(1)); 2610 var $matchingElement = $("#"+hash); 2611 // Sanity check that there's an element with that ID on the page 2612 if ($matchingElement.length) { 2613 // If the position of the target element is near the top of the page (<20px, where we expect it 2614 // to be because we need to move it down 60px to become in view), then move it down 60px 2615 if (Math.abs($matchingElement.offset().top - $(window).scrollTop()) < 20) { 2616 $(window).scrollTop($(window).scrollTop() - 60); 2617 } 2618 } 2619} 2620 2621// when an event on the browser history occurs (back, forward, load) requery hash and do search 2622$(window).hashchange( function(){ 2623 // Ignore if there's no search bar (some special pages have no header) 2624 if ($("#search-container").length < 1) return; 2625 2626 // If the hash isn't a search query or there's an error in the query, 2627 // then adjust the scroll position to account for sticky header, then exit. 2628 if ((location.hash.indexOf("q=") == -1) || (query == "undefined")) { 2629 // If the results pane is open, close it. 2630 if (!$("#searchResults").is(":hidden")) { 2631 hideResults(); 2632 } 2633 offsetScrollForSticky(); 2634 return; 2635 } 2636 2637 // Otherwise, we have a search to do 2638 var query = decodeURI(getQuery(location.hash)); 2639 searchControl.execute(query); 2640 $('#searchResults').slideDown('slow', setStickyTop); 2641 $("#search_autocomplete").focus(); 2642 $(".search .close").removeClass("hide"); 2643 2644 updateResultTitle(query); 2645}); 2646 2647function updateResultTitle(query) { 2648 $("#searchTitle").html("Results for <em>" + escapeHTML(query) + "</em>"); 2649} 2650 2651// forcefully regain key-up event control (previously jacked by search api) 2652$("#search_autocomplete").keyup(function(event) { 2653 return search_changed(event, false, toRoot); 2654}); 2655 2656// add event listeners to each tab so we can track the browser history 2657function addTabListeners() { 2658 var tabHeaders = $(".gsc-tabHeader"); 2659 for (var i = 0; i < tabHeaders.length; i++) { 2660 $(tabHeaders[i]).attr("id",i).click(function() { 2661 /* 2662 // make a copy of the page numbers for the search left pane 2663 setTimeout(function() { 2664 // remove any residual page numbers 2665 $('#searchResults .gsc-tabsArea .gsc-cursor-box.gs-bidi-start-align').remove(); 2666 // move the page numbers to the left position; make a clone, 2667 // because the element is drawn to the DOM only once 2668 // and because we're going to remove it (previous line), 2669 // we need it to be available to move again as the user navigates 2670 $('#searchResults .gsc-webResult .gsc-cursor-box.gs-bidi-start-align:visible') 2671 .clone().appendTo('#searchResults .gsc-tabsArea'); 2672 }, 200); 2673 */ 2674 }); 2675 } 2676 setTimeout(function(){$(tabHeaders[0]).click()},200); 2677} 2678 2679// add analytics tracking events to each result link 2680function addResultClickListeners() { 2681 $("#searchResults a.gs-title").each(function(index, link) { 2682 // When user clicks enter for Google search results, track it 2683 $(link).click(function() { 2684 ga('send', 'event', 'Google Click', 'clicked: ' + $(this).text(), 2685 'from: ' + $("#search_autocomplete").val()); 2686 }); 2687 }); 2688} 2689 2690 2691function getQuery(hash) { 2692 var queryParts = hash.split('='); 2693 return queryParts[1]; 2694} 2695 2696/* returns the given string with all HTML brackets converted to entities 2697 TODO: move this to the site's JS library */ 2698function escapeHTML(string) { 2699 return string.replace(/</g,"<") 2700 .replace(/>/g,">"); 2701} 2702 2703 2704 2705 2706 2707 2708 2709/* ######################################################## */ 2710/* ################# JAVADOC REFERENCE ################### */ 2711/* ######################################################## */ 2712 2713/* Initialize some droiddoc stuff, but only if we're in the reference */ 2714if (location.pathname.indexOf("/reference") == 0) { 2715 if(!(location.pathname.indexOf("/reference-gms/packages.html") == 0) 2716 && !(location.pathname.indexOf("/reference-gcm/packages.html") == 0) 2717 && !(location.pathname.indexOf("/reference/com/google") == 0)) { 2718 $(document).ready(function() { 2719 // init available apis based on user pref 2720 changeApiLevel(); 2721 initSidenavHeightResize() 2722 }); 2723 } 2724} 2725 2726var API_LEVEL_COOKIE = "api_level"; 2727var minLevel = 1; 2728var maxLevel = 1; 2729 2730/******* SIDENAV DIMENSIONS ************/ 2731 2732 function initSidenavHeightResize() { 2733 // Change the drag bar size to nicely fit the scrollbar positions 2734 var $dragBar = $(".ui-resizable-s"); 2735 $dragBar.css({'width': $dragBar.parent().width() - 5 + "px"}); 2736 2737 $( "#resize-packages-nav" ).resizable({ 2738 containment: "#nav-panels", 2739 handles: "s", 2740 alsoResize: "#packages-nav", 2741 resize: function(event, ui) { resizeNav(); }, /* resize the nav while dragging */ 2742 stop: function(event, ui) { saveNavPanels(); } /* once stopped, save the sizes to cookie */ 2743 }); 2744 2745 } 2746 2747function updateSidenavFixedWidth() { 2748 if (!sticky) return; 2749 $('#devdoc-nav').css({ 2750 'width' : $('#side-nav').css('width'), 2751 'margin' : $('#side-nav').css('margin') 2752 }); 2753 $('#devdoc-nav a.totop').css({'display':'block','width':$("#nav").innerWidth()+'px'}); 2754 2755 initSidenavHeightResize(); 2756} 2757 2758function updateSidenavFullscreenWidth() { 2759 if (!sticky) return; 2760 $('#devdoc-nav').css({ 2761 'width' : $('#side-nav').css('width'), 2762 'margin' : $('#side-nav').css('margin') 2763 }); 2764 $('#devdoc-nav .totop').css({'left': 'inherit'}); 2765 2766 initSidenavHeightResize(); 2767} 2768 2769function buildApiLevelSelector() { 2770 maxLevel = SINCE_DATA.length; 2771 var userApiLevel = parseInt(readCookie(API_LEVEL_COOKIE)); 2772 userApiLevel = userApiLevel == 0 ? maxLevel : userApiLevel; // If there's no cookie (zero), use the max by default 2773 2774 minLevel = parseInt($("#doc-api-level").attr("class")); 2775 // Handle provisional api levels; the provisional level will always be the highest possible level 2776 // Provisional api levels will also have a length; other stuff that's just missing a level won't, 2777 // so leave those kinds of entities at the default level of 1 (for example, the R.styleable class) 2778 if (isNaN(minLevel) && minLevel.length) { 2779 minLevel = maxLevel; 2780 } 2781 var select = $("#apiLevelSelector").html("").change(changeApiLevel); 2782 for (var i = maxLevel-1; i >= 0; i--) { 2783 var option = $("<option />").attr("value",""+SINCE_DATA[i]).append(""+SINCE_DATA[i]); 2784 // if (SINCE_DATA[i] < minLevel) option.addClass("absent"); // always false for strings (codenames) 2785 select.append(option); 2786 } 2787 2788 // get the DOM element and use setAttribute cuz IE6 fails when using jquery .attr('selected',true) 2789 var selectedLevelItem = $("#apiLevelSelector option[value='"+userApiLevel+"']").get(0); 2790 selectedLevelItem.setAttribute('selected',true); 2791} 2792 2793function changeApiLevel() { 2794 maxLevel = SINCE_DATA.length; 2795 var selectedLevel = maxLevel; 2796 2797 selectedLevel = parseInt($("#apiLevelSelector option:selected").val()); 2798 toggleVisisbleApis(selectedLevel, "body"); 2799 2800 writeCookie(API_LEVEL_COOKIE, selectedLevel, null); 2801 2802 if (selectedLevel < minLevel) { 2803 var thing = ($("#jd-header").html().indexOf("package") != -1) ? "package" : "class"; 2804 $("#naMessage").show().html("<div><p><strong>This " + thing 2805 + " requires API level " + minLevel + " or higher.</strong></p>" 2806 + "<p>This document is hidden because your selected API level for the documentation is " 2807 + selectedLevel + ". You can change the documentation API level with the selector " 2808 + "above the left navigation.</p>" 2809 + "<p>For more information about specifying the API level your app requires, " 2810 + "read <a href='" + toRoot + "training/basics/supporting-devices/platforms.html'" 2811 + ">Supporting Different Platform Versions</a>.</p>" 2812 + "<input type='button' value='OK, make this page visible' " 2813 + "title='Change the API level to " + minLevel + "' " 2814 + "onclick='$(\"#apiLevelSelector\").val(\"" + minLevel + "\");changeApiLevel();' />" 2815 + "</div>"); 2816 } else { 2817 $("#naMessage").hide(); 2818 } 2819} 2820 2821function toggleVisisbleApis(selectedLevel, context) { 2822 var apis = $(".api",context); 2823 apis.each(function(i) { 2824 var obj = $(this); 2825 var className = obj.attr("class"); 2826 var apiLevelIndex = className.lastIndexOf("-")+1; 2827 var apiLevelEndIndex = className.indexOf(" ", apiLevelIndex); 2828 apiLevelEndIndex = apiLevelEndIndex != -1 ? apiLevelEndIndex : className.length; 2829 var apiLevel = className.substring(apiLevelIndex, apiLevelEndIndex); 2830 if (apiLevel.length == 0) { // for odd cases when the since data is actually missing, just bail 2831 return; 2832 } 2833 apiLevel = parseInt(apiLevel); 2834 2835 // Handle provisional api levels; if this item's level is the provisional one, set it to the max 2836 var selectedLevelNum = parseInt(selectedLevel) 2837 var apiLevelNum = parseInt(apiLevel); 2838 if (isNaN(apiLevelNum)) { 2839 apiLevelNum = maxLevel; 2840 } 2841 2842 // Grey things out that aren't available and give a tooltip title 2843 if (apiLevelNum > selectedLevelNum) { 2844 obj.addClass("absent").attr("title","Requires API Level \"" 2845 + apiLevel + "\" or higher. To reveal, change the target API level " 2846 + "above the left navigation."); 2847 } 2848 else obj.removeClass("absent").removeAttr("title"); 2849 }); 2850} 2851 2852 2853 2854 2855/* ################# SIDENAV TREE VIEW ################### */ 2856 2857function new_node(me, mom, text, link, children_data, api_level) 2858{ 2859 var node = new Object(); 2860 node.children = Array(); 2861 node.children_data = children_data; 2862 node.depth = mom.depth + 1; 2863 2864 node.li = document.createElement("li"); 2865 mom.get_children_ul().appendChild(node.li); 2866 2867 node.label_div = document.createElement("div"); 2868 node.label_div.className = "label"; 2869 if (api_level != null) { 2870 $(node.label_div).addClass("api"); 2871 $(node.label_div).addClass("api-level-"+api_level); 2872 } 2873 node.li.appendChild(node.label_div); 2874 2875 if (children_data != null) { 2876 node.expand_toggle = document.createElement("a"); 2877 node.expand_toggle.href = "javascript:void(0)"; 2878 node.expand_toggle.onclick = function() { 2879 if (node.expanded) { 2880 $(node.get_children_ul()).slideUp("fast"); 2881 node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png"; 2882 node.expanded = false; 2883 } else { 2884 expand_node(me, node); 2885 } 2886 }; 2887 node.label_div.appendChild(node.expand_toggle); 2888 2889 node.plus_img = document.createElement("img"); 2890 node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png"; 2891 node.plus_img.className = "plus"; 2892 node.plus_img.width = "8"; 2893 node.plus_img.border = "0"; 2894 node.expand_toggle.appendChild(node.plus_img); 2895 2896 node.expanded = false; 2897 } 2898 2899 var a = document.createElement("a"); 2900 node.label_div.appendChild(a); 2901 node.label = document.createTextNode(text); 2902 a.appendChild(node.label); 2903 if (link) { 2904 a.href = me.toroot + link; 2905 } else { 2906 if (children_data != null) { 2907 a.className = "nolink"; 2908 a.href = "javascript:void(0)"; 2909 a.onclick = node.expand_toggle.onclick; 2910 // This next line shouldn't be necessary. I'll buy a beer for the first 2911 // person who figures out how to remove this line and have the link 2912 // toggle shut on the first try. --joeo@android.com 2913 node.expanded = false; 2914 } 2915 } 2916 2917 2918 node.children_ul = null; 2919 node.get_children_ul = function() { 2920 if (!node.children_ul) { 2921 node.children_ul = document.createElement("ul"); 2922 node.children_ul.className = "children_ul"; 2923 node.children_ul.style.display = "none"; 2924 node.li.appendChild(node.children_ul); 2925 } 2926 return node.children_ul; 2927 }; 2928 2929 return node; 2930} 2931 2932 2933 2934 2935function expand_node(me, node) 2936{ 2937 if (node.children_data && !node.expanded) { 2938 if (node.children_visited) { 2939 $(node.get_children_ul()).slideDown("fast"); 2940 } else { 2941 get_node(me, node); 2942 if ($(node.label_div).hasClass("absent")) { 2943 $(node.get_children_ul()).addClass("absent"); 2944 } 2945 $(node.get_children_ul()).slideDown("fast"); 2946 } 2947 node.plus_img.src = me.toroot + "assets/images/triangle-opened-small.png"; 2948 node.expanded = true; 2949 2950 // perform api level toggling because new nodes are new to the DOM 2951 var selectedLevel = $("#apiLevelSelector option:selected").val(); 2952 toggleVisisbleApis(selectedLevel, "#side-nav"); 2953 } 2954} 2955 2956function get_node(me, mom) 2957{ 2958 mom.children_visited = true; 2959 for (var i in mom.children_data) { 2960 var node_data = mom.children_data[i]; 2961 mom.children[i] = new_node(me, mom, node_data[0], node_data[1], 2962 node_data[2], node_data[3]); 2963 } 2964} 2965 2966function this_page_relative(toroot) 2967{ 2968 var full = document.location.pathname; 2969 var file = ""; 2970 if (toroot.substr(0, 1) == "/") { 2971 if (full.substr(0, toroot.length) == toroot) { 2972 return full.substr(toroot.length); 2973 } else { 2974 // the file isn't under toroot. Fail. 2975 return null; 2976 } 2977 } else { 2978 if (toroot != "./") { 2979 toroot = "./" + toroot; 2980 } 2981 do { 2982 if (toroot.substr(toroot.length-3, 3) == "../" || toroot == "./") { 2983 var pos = full.lastIndexOf("/"); 2984 file = full.substr(pos) + file; 2985 full = full.substr(0, pos); 2986 toroot = toroot.substr(0, toroot.length-3); 2987 } 2988 } while (toroot != "" && toroot != "/"); 2989 return file.substr(1); 2990 } 2991} 2992 2993function find_page(url, data) 2994{ 2995 var nodes = data; 2996 var result = null; 2997 for (var i in nodes) { 2998 var d = nodes[i]; 2999 if (d[1] == url) { 3000 return new Array(i); 3001 } 3002 else if (d[2] != null) { 3003 result = find_page(url, d[2]); 3004 if (result != null) { 3005 return (new Array(i).concat(result)); 3006 } 3007 } 3008 } 3009 return null; 3010} 3011 3012function init_default_navtree(toroot) { 3013 // load json file for navtree data 3014 $.getScript(toRoot + 'navtree_data.js', function(data, textStatus, jqxhr) { 3015 // when the file is loaded, initialize the tree 3016 if(jqxhr.status === 200) { 3017 init_navtree("tree-list", toroot, NAVTREE_DATA); 3018 } 3019 }); 3020 3021 // perform api level toggling because because the whole tree is new to the DOM 3022 var selectedLevel = $("#apiLevelSelector option:selected").val(); 3023 toggleVisisbleApis(selectedLevel, "#side-nav"); 3024} 3025 3026function init_navtree(navtree_id, toroot, root_nodes) 3027{ 3028 var me = new Object(); 3029 me.toroot = toroot; 3030 me.node = new Object(); 3031 3032 me.node.li = document.getElementById(navtree_id); 3033 me.node.children_data = root_nodes; 3034 me.node.children = new Array(); 3035 me.node.children_ul = document.createElement("ul"); 3036 me.node.get_children_ul = function() { return me.node.children_ul; }; 3037 //me.node.children_ul.className = "children_ul"; 3038 me.node.li.appendChild(me.node.children_ul); 3039 me.node.depth = 0; 3040 3041 get_node(me, me.node); 3042 3043 me.this_page = this_page_relative(toroot); 3044 me.breadcrumbs = find_page(me.this_page, root_nodes); 3045 if (me.breadcrumbs != null && me.breadcrumbs.length != 0) { 3046 var mom = me.node; 3047 for (var i in me.breadcrumbs) { 3048 var j = me.breadcrumbs[i]; 3049 mom = mom.children[j]; 3050 expand_node(me, mom); 3051 } 3052 mom.label_div.className = mom.label_div.className + " selected"; 3053 addLoadEvent(function() { 3054 scrollIntoView("nav-tree"); 3055 }); 3056 } 3057} 3058 3059 3060 3061 3062 3063 3064 3065 3066/* TODO: eliminate redundancy with non-google functions */ 3067function init_google_navtree(navtree_id, toroot, root_nodes) 3068{ 3069 var me = new Object(); 3070 me.toroot = toroot; 3071 me.node = new Object(); 3072 3073 me.node.li = document.getElementById(navtree_id); 3074 me.node.children_data = root_nodes; 3075 me.node.children = new Array(); 3076 me.node.children_ul = document.createElement("ul"); 3077 me.node.get_children_ul = function() { return me.node.children_ul; }; 3078 //me.node.children_ul.className = "children_ul"; 3079 me.node.li.appendChild(me.node.children_ul); 3080 me.node.depth = 0; 3081 3082 get_google_node(me, me.node); 3083} 3084 3085function new_google_node(me, mom, text, link, children_data, api_level) 3086{ 3087 var node = new Object(); 3088 var child; 3089 node.children = Array(); 3090 node.children_data = children_data; 3091 node.depth = mom.depth + 1; 3092 node.get_children_ul = function() { 3093 if (!node.children_ul) { 3094 node.children_ul = document.createElement("ul"); 3095 node.children_ul.className = "tree-list-children"; 3096 node.li.appendChild(node.children_ul); 3097 } 3098 return node.children_ul; 3099 }; 3100 node.li = document.createElement("li"); 3101 3102 mom.get_children_ul().appendChild(node.li); 3103 3104 3105 if(link) { 3106 child = document.createElement("a"); 3107 3108 } 3109 else { 3110 child = document.createElement("span"); 3111 child.className = "tree-list-subtitle"; 3112 3113 } 3114 if (children_data != null) { 3115 node.li.className="nav-section"; 3116 node.label_div = document.createElement("div"); 3117 node.label_div.className = "nav-section-header-ref"; 3118 node.li.appendChild(node.label_div); 3119 get_google_node(me, node); 3120 node.label_div.appendChild(child); 3121 } 3122 else { 3123 node.li.appendChild(child); 3124 } 3125 if(link) { 3126 child.href = me.toroot + link; 3127 } 3128 node.label = document.createTextNode(text); 3129 child.appendChild(node.label); 3130 3131 node.children_ul = null; 3132 3133 return node; 3134} 3135 3136function get_google_node(me, mom) 3137{ 3138 mom.children_visited = true; 3139 var linkText; 3140 for (var i in mom.children_data) { 3141 var node_data = mom.children_data[i]; 3142 linkText = node_data[0]; 3143 3144 if(linkText.match("^"+"com.google.android")=="com.google.android"){ 3145 linkText = linkText.substr(19, linkText.length); 3146 } 3147 mom.children[i] = new_google_node(me, mom, linkText, node_data[1], 3148 node_data[2], node_data[3]); 3149 } 3150} 3151 3152 3153 3154 3155 3156 3157/****** NEW version of script to build google and sample navs dynamically ******/ 3158// TODO: update Google reference docs to tolerate this new implementation 3159 3160var NODE_NAME = 0; 3161var NODE_HREF = 1; 3162var NODE_GROUP = 2; 3163var NODE_TAGS = 3; 3164var NODE_CHILDREN = 4; 3165 3166function init_google_navtree2(navtree_id, data) 3167{ 3168 var $containerUl = $("#"+navtree_id); 3169 for (var i in data) { 3170 var node_data = data[i]; 3171 $containerUl.append(new_google_node2(node_data)); 3172 } 3173 3174 // Make all third-generation list items 'sticky' to prevent them from collapsing 3175 $containerUl.find('li li li.nav-section').addClass('sticky'); 3176 3177 initExpandableNavItems("#"+navtree_id); 3178} 3179 3180function new_google_node2(node_data) 3181{ 3182 var linkText = node_data[NODE_NAME]; 3183 if(linkText.match("^"+"com.google.android")=="com.google.android"){ 3184 linkText = linkText.substr(19, linkText.length); 3185 } 3186 var $li = $('<li>'); 3187 var $a; 3188 if (node_data[NODE_HREF] != null) { 3189 $a = $('<a href="' + toRoot + node_data[NODE_HREF] + '" title="' + linkText + '" >' 3190 + linkText + '</a>'); 3191 } else { 3192 $a = $('<a href="#" onclick="return false;" title="' + linkText + '" >' 3193 + linkText + '/</a>'); 3194 } 3195 var $childUl = $('<ul>'); 3196 if (node_data[NODE_CHILDREN] != null) { 3197 $li.addClass("nav-section"); 3198 $a = $('<div class="nav-section-header">').append($a); 3199 if (node_data[NODE_HREF] == null) $a.addClass('empty'); 3200 3201 for (var i in node_data[NODE_CHILDREN]) { 3202 var child_node_data = node_data[NODE_CHILDREN][i]; 3203 $childUl.append(new_google_node2(child_node_data)); 3204 } 3205 $li.append($childUl); 3206 } 3207 $li.prepend($a); 3208 3209 return $li; 3210} 3211 3212 3213 3214 3215 3216 3217 3218 3219 3220 3221 3222function showGoogleRefTree() { 3223 init_default_google_navtree(toRoot); 3224 init_default_gcm_navtree(toRoot); 3225} 3226 3227function init_default_google_navtree(toroot) { 3228 // load json file for navtree data 3229 $.getScript(toRoot + 'gms_navtree_data.js', function(data, textStatus, jqxhr) { 3230 // when the file is loaded, initialize the tree 3231 if(jqxhr.status === 200) { 3232 init_google_navtree("gms-tree-list", toroot, GMS_NAVTREE_DATA); 3233 highlightSidenav(); 3234 resizeNav(); 3235 } 3236 }); 3237} 3238 3239function init_default_gcm_navtree(toroot) { 3240 // load json file for navtree data 3241 $.getScript(toRoot + 'gcm_navtree_data.js', function(data, textStatus, jqxhr) { 3242 // when the file is loaded, initialize the tree 3243 if(jqxhr.status === 200) { 3244 init_google_navtree("gcm-tree-list", toroot, GCM_NAVTREE_DATA); 3245 highlightSidenav(); 3246 resizeNav(); 3247 } 3248 }); 3249} 3250 3251function showSamplesRefTree() { 3252 init_default_samples_navtree(toRoot); 3253} 3254 3255function init_default_samples_navtree(toroot) { 3256 // load json file for navtree data 3257 $.getScript(toRoot + 'samples_navtree_data.js', function(data, textStatus, jqxhr) { 3258 // when the file is loaded, initialize the tree 3259 if(jqxhr.status === 200) { 3260 // hack to remove the "about the samples" link then put it back in 3261 // after we nuke the list to remove the dummy static list of samples 3262 var $firstLi = $("#nav.samples-nav > li:first-child").clone(); 3263 $("#nav.samples-nav").empty(); 3264 $("#nav.samples-nav").append($firstLi); 3265 3266 init_google_navtree2("nav.samples-nav", SAMPLES_NAVTREE_DATA); 3267 highlightSidenav(); 3268 resizeNav(); 3269 if ($("#jd-content #samples").length) { 3270 showSamples(); 3271 } 3272 } 3273 }); 3274} 3275 3276/* TOGGLE INHERITED MEMBERS */ 3277 3278/* Toggle an inherited class (arrow toggle) 3279 * @param linkObj The link that was clicked. 3280 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed. 3281 * 'null' to simply toggle. 3282 */ 3283function toggleInherited(linkObj, expand) { 3284 var base = linkObj.getAttribute("id"); 3285 var list = document.getElementById(base + "-list"); 3286 var summary = document.getElementById(base + "-summary"); 3287 var trigger = document.getElementById(base + "-trigger"); 3288 var a = $(linkObj); 3289 if ( (expand == null && a.hasClass("closed")) || expand ) { 3290 list.style.display = "none"; 3291 summary.style.display = "block"; 3292 trigger.src = toRoot + "assets/images/triangle-opened.png"; 3293 a.removeClass("closed"); 3294 a.addClass("opened"); 3295 } else if ( (expand == null && a.hasClass("opened")) || (expand == false) ) { 3296 list.style.display = "block"; 3297 summary.style.display = "none"; 3298 trigger.src = toRoot + "assets/images/triangle-closed.png"; 3299 a.removeClass("opened"); 3300 a.addClass("closed"); 3301 } 3302 return false; 3303} 3304 3305/* Toggle all inherited classes in a single table (e.g. all inherited methods) 3306 * @param linkObj The link that was clicked. 3307 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed. 3308 * 'null' to simply toggle. 3309 */ 3310function toggleAllInherited(linkObj, expand) { 3311 var a = $(linkObj); 3312 var table = $(a.parent().parent().parent()); // ugly way to get table/tbody 3313 var expandos = $(".jd-expando-trigger", table); 3314 if ( (expand == null && a.text() == "[Expand]") || expand ) { 3315 expandos.each(function(i) { 3316 toggleInherited(this, true); 3317 }); 3318 a.text("[Collapse]"); 3319 } else if ( (expand == null && a.text() == "[Collapse]") || (expand == false) ) { 3320 expandos.each(function(i) { 3321 toggleInherited(this, false); 3322 }); 3323 a.text("[Expand]"); 3324 } 3325 return false; 3326} 3327 3328/* Toggle all inherited members in the class (link in the class title) 3329 */ 3330function toggleAllClassInherited() { 3331 var a = $("#toggleAllClassInherited"); // get toggle link from class title 3332 var toggles = $(".toggle-all", $("#body-content")); 3333 if (a.text() == "[Expand All]") { 3334 toggles.each(function(i) { 3335 toggleAllInherited(this, true); 3336 }); 3337 a.text("[Collapse All]"); 3338 } else { 3339 toggles.each(function(i) { 3340 toggleAllInherited(this, false); 3341 }); 3342 a.text("[Expand All]"); 3343 } 3344 return false; 3345} 3346 3347/* Expand all inherited members in the class. Used when initiating page search */ 3348function ensureAllInheritedExpanded() { 3349 var toggles = $(".toggle-all", $("#body-content")); 3350 toggles.each(function(i) { 3351 toggleAllInherited(this, true); 3352 }); 3353 $("#toggleAllClassInherited").text("[Collapse All]"); 3354} 3355 3356 3357/* HANDLE KEY EVENTS 3358 * - Listen for Ctrl+F (Cmd on Mac) and expand all inherited members (to aid page search) 3359 */ 3360var agent = navigator['userAgent'].toLowerCase(); 3361var mac = agent.indexOf("macintosh") != -1; 3362 3363$(document).keydown( function(e) { 3364var control = mac ? e.metaKey && !e.ctrlKey : e.ctrlKey; // get ctrl key 3365 if (control && e.which == 70) { // 70 is "F" 3366 ensureAllInheritedExpanded(); 3367 } 3368}); 3369 3370 3371 3372 3373 3374 3375/* On-demand functions */ 3376 3377/** Move sample code line numbers out of PRE block and into non-copyable column */ 3378function initCodeLineNumbers() { 3379 var numbers = $("#codesample-block a.number"); 3380 if (numbers.length) { 3381 $("#codesample-line-numbers").removeClass("hidden").append(numbers); 3382 } 3383 3384 $(document).ready(function() { 3385 // select entire line when clicked 3386 $("span.code-line").click(function() { 3387 if (!shifted) { 3388 selectText(this); 3389 } 3390 }); 3391 // invoke line link on double click 3392 $(".code-line").dblclick(function() { 3393 document.location.hash = $(this).attr('id'); 3394 }); 3395 // highlight the line when hovering on the number 3396 $("#codesample-line-numbers a.number").mouseover(function() { 3397 var id = $(this).attr('href'); 3398 $(id).css('background','#e7e7e7'); 3399 }); 3400 $("#codesample-line-numbers a.number").mouseout(function() { 3401 var id = $(this).attr('href'); 3402 $(id).css('background','none'); 3403 }); 3404 }); 3405} 3406 3407// create SHIFT key binder to avoid the selectText method when selecting multiple lines 3408var shifted = false; 3409$(document).bind('keyup keydown', function(e){shifted = e.shiftKey; return true;} ); 3410 3411// courtesy of jasonedelman.com 3412function selectText(element) { 3413 var doc = document 3414 , range, selection 3415 ; 3416 if (doc.body.createTextRange) { //ms 3417 range = doc.body.createTextRange(); 3418 range.moveToElementText(element); 3419 range.select(); 3420 } else if (window.getSelection) { //all others 3421 selection = window.getSelection(); 3422 range = doc.createRange(); 3423 range.selectNodeContents(element); 3424 selection.removeAllRanges(); 3425 selection.addRange(range); 3426 } 3427} 3428 3429 3430 3431 3432/** Display links and other information about samples that match the 3433 group specified by the URL */ 3434function showSamples() { 3435 var group = $("#samples").attr('class'); 3436 $("#samples").html("<p>Here are some samples for <b>" + group + "</b> apps:</p>"); 3437 3438 var $ul = $("<ul>"); 3439 $selectedLi = $("#nav li.selected"); 3440 3441 $selectedLi.children("ul").children("li").each(function() { 3442 var $li = $("<li>").append($(this).find("a").first().clone()); 3443 $ul.append($li); 3444 }); 3445 3446 $("#samples").append($ul); 3447 3448} 3449 3450 3451 3452/* ########################################################## */ 3453/* ################### RESOURCE CARDS ##################### */ 3454/* ########################################################## */ 3455 3456/** Handle resource queries, collections, and grids (sections). Requires 3457 jd_tag_helpers.js and the *_unified_data.js to be loaded. */ 3458 3459(function() { 3460 // Prevent the same resource from being loaded more than once per page. 3461 var addedPageResources = {}; 3462 3463 $(document).ready(function() { 3464 $('.resource-widget').each(function() { 3465 initResourceWidget(this); 3466 }); 3467 3468 /* Pass the line height to ellipsisfade() to adjust the height of the 3469 text container to show the max number of lines possible, without 3470 showing lines that are cut off. This works with the css ellipsis 3471 classes to fade last text line and apply an ellipsis char. */ 3472 3473 //card text currently uses 15px line height. 3474 var lineHeight = 15; 3475 $('.card-info .text').ellipsisfade(lineHeight); 3476 }); 3477 3478 /* 3479 Three types of resource layouts: 3480 Flow - Uses a fixed row-height flow using float left style. 3481 Carousel - Single card slideshow all same dimension absolute. 3482 Stack - Uses fixed columns and flexible element height. 3483 */ 3484 function initResourceWidget(widget) { 3485 var $widget = $(widget); 3486 var isFlow = $widget.hasClass('resource-flow-layout'), 3487 isCarousel = $widget.hasClass('resource-carousel-layout'), 3488 isStack = $widget.hasClass('resource-stack-layout'); 3489 3490 // find size of widget by pulling out its class name 3491 var sizeCols = 1; 3492 var m = $widget.get(0).className.match(/\bcol-(\d+)\b/); 3493 if (m) { 3494 sizeCols = parseInt(m[1], 10); 3495 } 3496 3497 var opts = { 3498 cardSizes: ($widget.data('cardsizes') || '').split(','), 3499 maxResults: parseInt($widget.data('maxresults') || '100', 10), 3500 itemsPerPage: $widget.data('itemsperpage'), 3501 sortOrder: $widget.data('sortorder'), 3502 query: $widget.data('query'), 3503 section: $widget.data('section'), 3504 sizeCols: sizeCols, 3505 /* Added by LFL 6/6/14 */ 3506 resourceStyle: $widget.data('resourcestyle') || 'card', 3507 stackSort: $widget.data('stacksort') || 'true' 3508 }; 3509 3510 // run the search for the set of resources to show 3511 3512 var resources = buildResourceList(opts); 3513 3514 if (isFlow) { 3515 drawResourcesFlowWidget($widget, opts, resources); 3516 } else if (isCarousel) { 3517 drawResourcesCarouselWidget($widget, opts, resources); 3518 } else if (isStack) { 3519 /* Looks like this got removed and is not used, so repurposing for the 3520 homepage style layout. 3521 Modified by LFL 6/6/14 3522 */ 3523 //var sections = buildSectionList(opts); 3524 opts['numStacks'] = $widget.data('numstacks'); 3525 drawResourcesStackWidget($widget, opts, resources/*, sections*/); 3526 } 3527 } 3528 3529 /* Initializes a Resource Carousel Widget */ 3530 function drawResourcesCarouselWidget($widget, opts, resources) { 3531 $widget.empty(); 3532 var plusone = true; //always show plusone on carousel 3533 3534 $widget.addClass('resource-card slideshow-container') 3535 .append($('<a>').addClass('slideshow-prev').text('Prev')) 3536 .append($('<a>').addClass('slideshow-next').text('Next')); 3537 3538 var css = { 'width': $widget.width() + 'px', 3539 'height': $widget.height() + 'px' }; 3540 3541 var $ul = $('<ul>'); 3542 3543 for (var i = 0; i < resources.length; ++i) { 3544 var $card = $('<a>') 3545 .attr('href', cleanUrl(resources[i].url)) 3546 .decorateResourceCard(resources[i],plusone); 3547 3548 $('<li>').css(css) 3549 .append($card) 3550 .appendTo($ul); 3551 } 3552 3553 $('<div>').addClass('frame') 3554 .append($ul) 3555 .appendTo($widget); 3556 3557 $widget.dacSlideshow({ 3558 auto: true, 3559 btnPrev: '.slideshow-prev', 3560 btnNext: '.slideshow-next' 3561 }); 3562 }; 3563 3564 /* Initializes a Resource Card Stack Widget (column-based layout) 3565 Modified by LFL 6/6/14 3566 */ 3567 function drawResourcesStackWidget($widget, opts, resources, sections) { 3568 // Don't empty widget, grab all items inside since they will be the first 3569 // items stacked, followed by the resource query 3570 var plusone = true; //by default show plusone on section cards 3571 var cards = $widget.find('.resource-card').detach().toArray(); 3572 var numStacks = opts.numStacks || 1; 3573 var $stacks = []; 3574 var urlString; 3575 3576 for (var i = 0; i < numStacks; ++i) { 3577 $stacks[i] = $('<div>').addClass('resource-card-stack') 3578 .appendTo($widget); 3579 } 3580 3581 var sectionResources = []; 3582 3583 // Extract any subsections that are actually resource cards 3584 if (sections) { 3585 for (var i = 0; i < sections.length; ++i) { 3586 if (!sections[i].sections || !sections[i].sections.length) { 3587 // Render it as a resource card 3588 sectionResources.push( 3589 $('<a>') 3590 .addClass('resource-card section-card') 3591 .attr('href', cleanUrl(sections[i].resource.url)) 3592 .decorateResourceCard(sections[i].resource,plusone)[0] 3593 ); 3594 3595 } else { 3596 cards.push( 3597 $('<div>') 3598 .addClass('resource-card section-card-menu') 3599 .decorateResourceSection(sections[i],plusone)[0] 3600 ); 3601 } 3602 } 3603 } 3604 3605 cards = cards.concat(sectionResources); 3606 3607 for (var i = 0; i < resources.length; ++i) { 3608 var $card = createResourceElement(resources[i], opts); 3609 3610 if (opts.resourceStyle.indexOf('related') > -1) { 3611 $card.addClass('related-card'); 3612 } 3613 3614 cards.push($card[0]); 3615 } 3616 3617 if (opts.stackSort != 'false') { 3618 for (var i = 0; i < cards.length; ++i) { 3619 // Find the stack with the shortest height, but give preference to 3620 // left to right order. 3621 var minHeight = $stacks[0].height(); 3622 var minIndex = 0; 3623 3624 for (var j = 1; j < numStacks; ++j) { 3625 var height = $stacks[j].height(); 3626 if (height < minHeight - 45) { 3627 minHeight = height; 3628 minIndex = j; 3629 } 3630 } 3631 3632 $stacks[minIndex].append($(cards[i])); 3633 } 3634 } 3635 3636 }; 3637 3638 /* 3639 Create a resource card using the given resource object and a list of html 3640 configured options. Returns a jquery object containing the element. 3641 */ 3642 function createResourceElement(resource, opts, plusone) { 3643 var $el; 3644 3645 // The difference here is that generic cards are not entirely clickable 3646 // so its a div instead of an a tag, also the generic one is not given 3647 // the resource-card class so it appears with a transparent background 3648 // and can be styled in whatever way the css setup. 3649 if (opts.resourceStyle == 'generic') { 3650 $el = $('<div>') 3651 .addClass('resource') 3652 .attr('href', cleanUrl(resource.url)) 3653 .decorateResource(resource, opts); 3654 } else { 3655 var cls = 'resource resource-card'; 3656 3657 $el = $('<a>') 3658 .addClass(cls) 3659 .attr('href', cleanUrl(resource.url)) 3660 .decorateResourceCard(resource, plusone); 3661 } 3662 3663 return $el; 3664 } 3665 3666 /* Initializes a flow widget, see distribute.scss for generating accompanying css */ 3667 function drawResourcesFlowWidget($widget, opts, resources) { 3668 $widget.empty(); 3669 var cardSizes = opts.cardSizes || ['6x6']; 3670 var i = 0, j = 0; 3671 var plusone = true; // by default show plusone on resource cards 3672 3673 while (i < resources.length) { 3674 var cardSize = cardSizes[j++ % cardSizes.length]; 3675 cardSize = cardSize.replace(/^\s+|\s+$/,''); 3676 // Some card sizes do not get a plusone button, such as where space is constrained 3677 // or for cards commonly embedded in docs (to improve overall page speed). 3678 plusone = !((cardSize == "6x2") || (cardSize == "6x3") || 3679 (cardSize == "9x2") || (cardSize == "9x3") || 3680 (cardSize == "12x2") || (cardSize == "12x3")); 3681 3682 // A stack has a third dimension which is the number of stacked items 3683 var isStack = cardSize.match(/(\d+)x(\d+)x(\d+)/); 3684 var stackCount = 0; 3685 var $stackDiv = null; 3686 3687 if (isStack) { 3688 // Create a stack container which should have the dimensions defined 3689 // by the product of the items inside. 3690 $stackDiv = $('<div>').addClass('resource-card-stack resource-card-' + isStack[1] 3691 + 'x' + isStack[2] * isStack[3]) .appendTo($widget); 3692 } 3693 3694 // Build each stack item or just a single item 3695 do { 3696 var resource = resources[i]; 3697 3698 var $card = createResourceElement(resources[i], opts, plusone); 3699 3700 $card.addClass('resource-card-' + cardSize + 3701 ' resource-card-' + resource.type); 3702 3703 if (isStack) { 3704 $card.addClass('resource-card-' + isStack[1] + 'x' + isStack[2]); 3705 if (++stackCount == parseInt(isStack[3])) { 3706 $card.addClass('resource-card-row-stack-last'); 3707 stackCount = 0; 3708 } 3709 } else { 3710 stackCount = 0; 3711 } 3712 3713 $card.appendTo($stackDiv || $widget); 3714 3715 } while (++i < resources.length && stackCount > 0); 3716 } 3717 } 3718 3719 /* Build a site map of resources using a section as a root. */ 3720 function buildSectionList(opts) { 3721 if (opts.section && SECTION_BY_ID[opts.section]) { 3722 return SECTION_BY_ID[opts.section].sections || []; 3723 } 3724 return []; 3725 } 3726 3727 function buildResourceList(opts) { 3728 var maxResults = opts.maxResults || 100; 3729 3730 var query = opts.query || ''; 3731 var expressions = parseResourceQuery(query); 3732 var addedResourceIndices = {}; 3733 var results = []; 3734 3735 for (var i = 0; i < expressions.length; i++) { 3736 var clauses = expressions[i]; 3737 3738 // build initial set of resources from first clause 3739 var firstClause = clauses[0]; 3740 var resources = []; 3741 switch (firstClause.attr) { 3742 case 'type': 3743 resources = ALL_RESOURCES_BY_TYPE[firstClause.value]; 3744 break; 3745 case 'lang': 3746 resources = ALL_RESOURCES_BY_LANG[firstClause.value]; 3747 break; 3748 case 'tag': 3749 resources = ALL_RESOURCES_BY_TAG[firstClause.value]; 3750 break; 3751 case 'collection': 3752 var urls = RESOURCE_COLLECTIONS[firstClause.value].resources || []; 3753 resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; }); 3754 break; 3755 case 'section': 3756 var urls = SITE_MAP[firstClause.value].sections || []; 3757 resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; }); 3758 break; 3759 } 3760 // console.log(firstClause.attr + ':' + firstClause.value); 3761 resources = resources || []; 3762 3763 // use additional clauses to filter corpus 3764 if (clauses.length > 1) { 3765 var otherClauses = clauses.slice(1); 3766 resources = resources.filter(getResourceMatchesClausesFilter(otherClauses)); 3767 } 3768 3769 // filter out resources already added 3770 if (i > 1) { 3771 resources = resources.filter(getResourceNotAlreadyAddedFilter(addedResourceIndices)); 3772 } 3773 3774 // add to list of already added indices 3775 for (var j = 0; j < resources.length; j++) { 3776 // console.log(resources[j].title); 3777 addedResourceIndices[resources[j].index] = 1; 3778 } 3779 3780 // concat to final results list 3781 results = results.concat(resources); 3782 } 3783 3784 if (opts.sortOrder && results.length) { 3785 var attr = opts.sortOrder; 3786 3787 if (opts.sortOrder == 'random') { 3788 var i = results.length, j, temp; 3789 while (--i) { 3790 j = Math.floor(Math.random() * (i + 1)); 3791 temp = results[i]; 3792 results[i] = results[j]; 3793 results[j] = temp; 3794 } 3795 } else { 3796 var desc = attr.charAt(0) == '-'; 3797 if (desc) { 3798 attr = attr.substring(1); 3799 } 3800 results = results.sort(function(x,y) { 3801 return (desc ? -1 : 1) * (parseInt(x[attr], 10) - parseInt(y[attr], 10)); 3802 }); 3803 } 3804 } 3805 3806 results = results.filter(getResourceNotAlreadyAddedFilter(addedPageResources)); 3807 results = results.slice(0, maxResults); 3808 3809 for (var j = 0; j < results.length; ++j) { 3810 addedPageResources[results[j].index] = 1; 3811 } 3812 3813 return results; 3814 } 3815 3816 3817 function getResourceNotAlreadyAddedFilter(addedResourceIndices) { 3818 return function(resource) { 3819 return !addedResourceIndices[resource.index]; 3820 }; 3821 } 3822 3823 3824 function getResourceMatchesClausesFilter(clauses) { 3825 return function(resource) { 3826 return doesResourceMatchClauses(resource, clauses); 3827 }; 3828 } 3829 3830 3831 function doesResourceMatchClauses(resource, clauses) { 3832 for (var i = 0; i < clauses.length; i++) { 3833 var map; 3834 switch (clauses[i].attr) { 3835 case 'type': 3836 map = IS_RESOURCE_OF_TYPE[clauses[i].value]; 3837 break; 3838 case 'lang': 3839 map = IS_RESOURCE_IN_LANG[clauses[i].value]; 3840 break; 3841 case 'tag': 3842 map = IS_RESOURCE_TAGGED[clauses[i].value]; 3843 break; 3844 } 3845 3846 if (!map || (!!clauses[i].negative ? map[resource.index] : !map[resource.index])) { 3847 return clauses[i].negative; 3848 } 3849 } 3850 return true; 3851 } 3852 3853 function cleanUrl(url) 3854 { 3855 if (url && url.indexOf('//') === -1) { 3856 url = toRoot + url; 3857 } 3858 3859 return url; 3860 } 3861 3862 3863 function parseResourceQuery(query) { 3864 // Parse query into array of expressions (expression e.g. 'tag:foo + type:video') 3865 var expressions = []; 3866 var expressionStrs = query.split(',') || []; 3867 for (var i = 0; i < expressionStrs.length; i++) { 3868 var expr = expressionStrs[i] || ''; 3869 3870 // Break expression into clauses (clause e.g. 'tag:foo') 3871 var clauses = []; 3872 var clauseStrs = expr.split(/(?=[\+\-])/); 3873 for (var j = 0; j < clauseStrs.length; j++) { 3874 var clauseStr = clauseStrs[j] || ''; 3875 3876 // Get attribute and value from clause (e.g. attribute='tag', value='foo') 3877 var parts = clauseStr.split(':'); 3878 var clause = {}; 3879 3880 clause.attr = parts[0].replace(/^\s+|\s+$/g,''); 3881 if (clause.attr) { 3882 if (clause.attr.charAt(0) == '+') { 3883 clause.attr = clause.attr.substring(1); 3884 } else if (clause.attr.charAt(0) == '-') { 3885 clause.negative = true; 3886 clause.attr = clause.attr.substring(1); 3887 } 3888 } 3889 3890 if (parts.length > 1) { 3891 clause.value = parts[1].replace(/^\s+|\s+$/g,''); 3892 } 3893 3894 clauses.push(clause); 3895 } 3896 3897 if (!clauses.length) { 3898 continue; 3899 } 3900 3901 expressions.push(clauses); 3902 } 3903 3904 return expressions; 3905 } 3906})(); 3907 3908(function($) { 3909 3910 /* 3911 Utility method for creating dom for the description area of a card. 3912 Used in decorateResourceCard and decorateResource. 3913 */ 3914 function buildResourceCardDescription(resource, plusone) { 3915 var $description = $('<div>').addClass('description ellipsis'); 3916 3917 $description.append($('<div>').addClass('text').html(resource.summary)); 3918 3919 if (resource.cta) { 3920 $description.append($('<a>').addClass('cta').html(resource.cta)); 3921 } 3922 3923 if (plusone) { 3924 var plusurl = resource.url.indexOf("//") > -1 ? resource.url : 3925 "//developer.android.com/" + resource.url; 3926 3927 $description.append($('<div>').addClass('util') 3928 .append($('<div>').addClass('g-plusone') 3929 .attr('data-size', 'small') 3930 .attr('data-align', 'right') 3931 .attr('data-href', plusurl))); 3932 } 3933 3934 return $description; 3935 } 3936 3937 3938 /* Simple jquery function to create dom for a standard resource card */ 3939 $.fn.decorateResourceCard = function(resource,plusone) { 3940 var section = resource.group || resource.type; 3941 var imgUrl = resource.image || 3942 'assets/images/resource-card-default-android.jpg'; 3943 3944 if (imgUrl.indexOf('//') === -1) { 3945 imgUrl = toRoot + imgUrl; 3946 } 3947 3948 $('<div>').addClass('card-bg') 3949 .css('background-image', 'url(' + (imgUrl || toRoot + 3950 'assets/images/resource-card-default-android.jpg') + ')') 3951 .appendTo(this); 3952 3953 $('<div>').addClass('card-info' + (!resource.summary ? ' empty-desc' : '')) 3954 .append($('<div>').addClass('section').text(section)) 3955 .append($('<div>').addClass('title').html(resource.title)) 3956 .append(buildResourceCardDescription(resource, plusone)) 3957 .appendTo(this); 3958 3959 return this; 3960 }; 3961 3962 /* Simple jquery function to create dom for a resource section card (menu) */ 3963 $.fn.decorateResourceSection = function(section,plusone) { 3964 var resource = section.resource; 3965 //keep url clean for matching and offline mode handling 3966 var urlPrefix = resource.image.indexOf("//") > -1 ? "" : toRoot; 3967 var $base = $('<a>') 3968 .addClass('card-bg') 3969 .attr('href', resource.url) 3970 .append($('<div>').addClass('card-section-icon') 3971 .append($('<div>').addClass('icon')) 3972 .append($('<div>').addClass('section').html(resource.title))) 3973 .appendTo(this); 3974 3975 var $cardInfo = $('<div>').addClass('card-info').appendTo(this); 3976 3977 if (section.sections && section.sections.length) { 3978 // Recurse the section sub-tree to find a resource image. 3979 var stack = [section]; 3980 3981 while (stack.length) { 3982 if (stack[0].resource.image) { 3983 $base.css('background-image', 'url(' + urlPrefix + stack[0].resource.image + ')'); 3984 break; 3985 } 3986 3987 if (stack[0].sections) { 3988 stack = stack.concat(stack[0].sections); 3989 } 3990 3991 stack.shift(); 3992 } 3993 3994 var $ul = $('<ul>') 3995 .appendTo($cardInfo); 3996 3997 var max = section.sections.length > 3 ? 3 : section.sections.length; 3998 3999 for (var i = 0; i < max; ++i) { 4000 4001 var subResource = section.sections[i]; 4002 if (!plusone) { 4003 $('<li>') 4004 .append($('<a>').attr('href', subResource.url) 4005 .append($('<div>').addClass('title').html(subResource.title)) 4006 .append($('<div>').addClass('description ellipsis') 4007 .append($('<div>').addClass('text').html(subResource.summary)) 4008 .append($('<div>').addClass('util')))) 4009 .appendTo($ul); 4010 } else { 4011 $('<li>') 4012 .append($('<a>').attr('href', subResource.url) 4013 .append($('<div>').addClass('title').html(subResource.title)) 4014 .append($('<div>').addClass('description ellipsis') 4015 .append($('<div>').addClass('text').html(subResource.summary)) 4016 .append($('<div>').addClass('util') 4017 .append($('<div>').addClass('g-plusone') 4018 .attr('data-size', 'small') 4019 .attr('data-align', 'right') 4020 .attr('data-href', resource.url))))) 4021 .appendTo($ul); 4022 } 4023 } 4024 4025 // Add a more row 4026 if (max < section.sections.length) { 4027 $('<li>') 4028 .append($('<a>').attr('href', resource.url) 4029 .append($('<div>') 4030 .addClass('title') 4031 .text('More'))) 4032 .appendTo($ul); 4033 } 4034 } else { 4035 // No sub-resources, just render description? 4036 } 4037 4038 return this; 4039 }; 4040 4041 4042 4043 4044 /* Render other types of resource styles that are not cards. */ 4045 $.fn.decorateResource = function(resource, opts) { 4046 var imgUrl = resource.image || 4047 'assets/images/resource-card-default-android.jpg'; 4048 var linkUrl = resource.url; 4049 4050 if (imgUrl.indexOf('//') === -1) { 4051 imgUrl = toRoot + imgUrl; 4052 } 4053 4054 if (linkUrl && linkUrl.indexOf('//') === -1) { 4055 linkUrl = toRoot + linkUrl; 4056 } 4057 4058 $(this).append( 4059 $('<div>').addClass('image') 4060 .css('background-image', 'url(' + imgUrl + ')'), 4061 $('<div>').addClass('info').append( 4062 $('<h4>').addClass('title').html(resource.title), 4063 $('<p>').addClass('summary').html(resource.summary), 4064 $('<a>').attr('href', linkUrl).addClass('cta').html('Learn More') 4065 ) 4066 ); 4067 4068 return this; 4069 }; 4070})(jQuery); 4071 4072 4073/* Calculate the vertical area remaining */ 4074(function($) { 4075 $.fn.ellipsisfade= function(lineHeight) { 4076 this.each(function() { 4077 // get element text 4078 var $this = $(this); 4079 var remainingHeight = $this.parent().parent().height(); 4080 $this.parent().siblings().each(function () 4081 { 4082 if ($(this).is(":visible")) { 4083 var h = $(this).height(); 4084 remainingHeight = remainingHeight - h; 4085 } 4086 }); 4087 4088 adjustedRemainingHeight = ((remainingHeight)/lineHeight>>0)*lineHeight 4089 $this.parent().css({'height': adjustedRemainingHeight}); 4090 $this.css({'height': "auto"}); 4091 }); 4092 4093 return this; 4094 }; 4095}) (jQuery); 4096 4097/* 4098 Fullscreen Carousel 4099 4100 The following allows for an area at the top of the page that takes over the 4101 entire browser height except for its top offset and an optional bottom 4102 padding specified as a data attribute. 4103 4104 HTML: 4105 4106 <div class="fullscreen-carousel"> 4107 <div class="fullscreen-carousel-content"> 4108 <!-- content here --> 4109 </div> 4110 <div class="fullscreen-carousel-content"> 4111 <!-- content here --> 4112 </div> 4113 4114 etc ... 4115 4116 </div> 4117 4118 Control over how the carousel takes over the screen can mostly be defined in 4119 a css file. Setting min-height on the .fullscreen-carousel-content elements 4120 will prevent them from shrinking to far vertically when the browser is very 4121 short, and setting max-height on the .fullscreen-carousel itself will prevent 4122 the area from becoming to long in the case that the browser is stretched very 4123 tall. 4124 4125 There is limited functionality for having multiple sections since that request 4126 was removed, but it is possible to add .next-arrow and .prev-arrow elements to 4127 scroll between multiple content areas. 4128*/ 4129 4130(function() { 4131 $(document).ready(function() { 4132 $('.fullscreen-carousel').each(function() { 4133 initWidget(this); 4134 }); 4135 }); 4136 4137 function initWidget(widget) { 4138 var $widget = $(widget); 4139 4140 var topOffset = $widget.offset().top; 4141 var padBottom = parseInt($widget.data('paddingbottom')) || 0; 4142 var maxHeight = 0; 4143 var minHeight = 0; 4144 var $content = $widget.find('.fullscreen-carousel-content'); 4145 var $nextArrow = $widget.find('.next-arrow'); 4146 var $prevArrow = $widget.find('.prev-arrow'); 4147 var $curSection = $($content[0]); 4148 4149 if ($content.length <= 1) { 4150 $nextArrow.hide(); 4151 $prevArrow.hide(); 4152 } else { 4153 $nextArrow.click(function() { 4154 var index = ($content.index($curSection) + 1); 4155 $curSection.hide(); 4156 $curSection = $($content[index >= $content.length ? 0 : index]); 4157 $curSection.show(); 4158 }); 4159 4160 $prevArrow.click(function() { 4161 var index = ($content.index($curSection) - 1); 4162 $curSection.hide(); 4163 $curSection = $($content[index < 0 ? $content.length - 1 : 0]); 4164 $curSection.show(); 4165 }); 4166 } 4167 4168 // Just hide all content sections except first. 4169 $content.each(function(index) { 4170 if ($(this).height() > minHeight) minHeight = $(this).height(); 4171 $(this).css({position: 'absolute', display: index > 0 ? 'none' : ''}); 4172 }); 4173 4174 // Register for changes to window size, and trigger. 4175 $(window).resize(resizeWidget); 4176 resizeWidget(); 4177 4178 function resizeWidget() { 4179 var height = $(window).height() - topOffset - padBottom; 4180 $widget.width($(window).width()); 4181 $widget.height(height < minHeight ? minHeight : 4182 (maxHeight && height > maxHeight ? maxHeight : height)); 4183 } 4184 } 4185})(); 4186 4187 4188 4189 4190 4191/* 4192 Tab Carousel 4193 4194 The following allows tab widgets to be installed via the html below. Each 4195 tab content section should have a data-tab attribute matching one of the 4196 nav items'. Also each tab content section should have a width matching the 4197 tab carousel. 4198 4199 HTML: 4200 4201 <div class="tab-carousel"> 4202 <ul class="tab-nav"> 4203 <li><a href="#" data-tab="handsets">Handsets</a> 4204 <li><a href="#" data-tab="wearable">Wearable</a> 4205 <li><a href="#" data-tab="tv">TV</a> 4206 </ul> 4207 4208 <div class="tab-carousel-content"> 4209 <div data-tab="handsets"> 4210 <!--Full width content here--> 4211 </div> 4212 4213 <div data-tab="wearable"> 4214 <!--Full width content here--> 4215 </div> 4216 4217 <div data-tab="tv"> 4218 <!--Full width content here--> 4219 </div> 4220 </div> 4221 </div> 4222 4223*/ 4224(function() { 4225 $(document).ready(function() { 4226 $('.tab-carousel').each(function() { 4227 initWidget(this); 4228 }); 4229 }); 4230 4231 function initWidget(widget) { 4232 var $widget = $(widget); 4233 var $nav = $widget.find('.tab-nav'); 4234 var $anchors = $nav.find('[data-tab]'); 4235 var $li = $nav.find('li'); 4236 var $contentContainer = $widget.find('.tab-carousel-content'); 4237 var $tabs = $contentContainer.find('[data-tab]'); 4238 var $curTab = $($tabs[0]); // Current tab is first tab. 4239 var width = $widget.width(); 4240 4241 // Setup nav interactivity. 4242 $anchors.click(function(evt) { 4243 evt.preventDefault(); 4244 var query = '[data-tab=' + $(this).data('tab') + ']'; 4245 transitionWidget($tabs.filter(query)); 4246 }); 4247 4248 // Add highlight for navigation on first item. 4249 var $highlight = $('<div>').addClass('highlight') 4250 .css({left:$li.position().left + 'px', width:$li.outerWidth() + 'px'}) 4251 .appendTo($nav); 4252 4253 // Store height since we will change contents to absolute. 4254 $contentContainer.height($contentContainer.height()); 4255 4256 // Absolutely position tabs so they're ready for transition. 4257 $tabs.each(function(index) { 4258 $(this).css({position: 'absolute', left: index > 0 ? width + 'px' : '0'}); 4259 }); 4260 4261 function transitionWidget($toTab) { 4262 if (!$curTab.is($toTab)) { 4263 var curIndex = $tabs.index($curTab[0]); 4264 var toIndex = $tabs.index($toTab[0]); 4265 var dir = toIndex > curIndex ? 1 : -1; 4266 4267 // Animate content sections. 4268 $toTab.css({left:(width * dir) + 'px'}); 4269 $curTab.animate({left:(width * -dir) + 'px'}); 4270 $toTab.animate({left:'0'}); 4271 4272 // Animate navigation highlight. 4273 $highlight.animate({left:$($li[toIndex]).position().left + 'px', 4274 width:$($li[toIndex]).outerWidth() + 'px'}) 4275 4276 // Store new current section. 4277 $curTab = $toTab; 4278 } 4279 } 4280 } 4281})(); 4282