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