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