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