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