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