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