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