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