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