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