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